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

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

Issue 2339193003: A collector for postmortem reports (Closed)
Patch Set: Address first round of comments 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 std::unique_ptr<StabilityReport> Collect(
97 const base::FilePath& debug_state_file) override {
98 return std::unique_ptr<StabilityReport>(CollectRaw(debug_state_file));
99 }
100
101 MOCK_METHOD3(GetDebugStateFilePaths,
102 std::vector<base::FilePath>(
103 const base::FilePath& debug_info_dir,
104 const base::FilePath::StringType& debug_file_pattern,
105 const std::set<base::FilePath>&));
106 MOCK_METHOD1(CollectRaw, StabilityReport*(const base::FilePath&));
107 MOCK_METHOD4(WriteReportToMinidump,
108 bool(const StabilityReport& report,
109 const crashpad::UUID& client_id,
110 const crashpad::UUID& report_id,
111 base::PlatformFile minidump_file));
112 };
113
114 // Checks if two proto messages are the same based on their serializations. Note
115 // this only works if serialization is deterministic, which is not guaranteed.
116 // In practice, serialization is deterministic (even for protocol buffers with
117 // maps) and such matchers are common in the Chromium code base. Also note that
118 // in the context of this test, false positive matches are the problem and these
119 // are not possible (otherwise serialization would be ambiguous). False
120 // negatives would lead to test failure and developer action. Alternatives are:
121 // 1) a generic matcher (likely not possible without reflections, missing from
122 // lite runtime), 2) a specialized matcher or 3) implementing deterministic
123 // serialization.
124 // TODO(manzagop): switch a matcher with guarantees.
125 MATCHER_P(EqualsProto, message, "") {
126 std::string expected_serialized;
127 std::string actual_serialized;
128 message.SerializeToString(&expected_serialized);
129 arg.SerializeToString(&actual_serialized);
130 return expected_serialized == actual_serialized;
131 }
132
133 } // namespace
134
135 class PostmortemReportCollectorCollectAndSubmitTest : public testing::Test {
136 public:
137 void SetUp() override {
138 testing::Test::SetUp();
139 // Create a dummy debug file.
140 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
141 debug_file_ = temp_dir_.path().AppendASCII("foo-1.pma");
142 {
143 base::ScopedFILE file(base::OpenFile(debug_file_, "w"));
144 ASSERT_NE(file.get(), nullptr);
145 }
146 ASSERT_TRUE(base::PathExists(debug_file_));
147
148 // Expect collection of the debug file paths.
149 debug_file_pattern_ = FILE_PATH_LITERAL("foo-*.pma");
150 std::vector<base::FilePath> debug_files{debug_file_};
151 EXPECT_CALL(collector_,
152 GetDebugStateFilePaths(debug_file_.DirName(),
153 debug_file_pattern_, no_excluded_files_))
154 .Times(1)
155 .WillOnce(Return(debug_files));
156
157 EXPECT_CALL(database_, GetSettings()).Times(1).WillOnce(Return(nullptr));
158
159 // Expect collection to a proto of a single debug file.
160 // Note: caller takes ownership.
161 StabilityReport* stability_report = new StabilityReport();
162 EXPECT_CALL(collector_, CollectRaw(debug_file_))
163 .Times(1)
164 .WillOnce(Return(stability_report));
165
166 // Expect the call to write the proto to a minidump. This involves
167 // requesting
168 // a report from the crashpad database, writing the report, then finalizing
169 // it
170 // 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 postmortem_cnt = collector_.CollectAndSubmitForUpload(
207 debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
208 &database_);
209 ASSERT_EQ(1, postmortem_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 postmortem_cnt = collector_.CollectAndSubmitForUpload(
226 debug_file_.DirName(), debug_file_pattern_, no_excluded_files_,
227 &database_);
228 ASSERT_EQ(1, postmortem_cnt); // Note
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 returns nullptr.
283 PostmortemReportCollector collector;
284 ASSERT_EQ(nullptr, collector.Collect(file_path));
285 }
286
287 TEST(PostmortemReportCollectorTest, CollectRandomFile) {
288 // Create a file with content we don't expect to be valid for a debug file.
289 base::ScopedTempDir temp_dir;
290 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
291 base::FilePath file_path = temp_dir.path().AppendASCII("invalid_content.pma");
292 {
293 base::ScopedFILE file(base::OpenFile(file_path, "w"));
294 ASSERT_NE(file.get(), nullptr);
295 // Assuming this size is greater than the minimum size of a debug file.
296 std::vector<uint8_t> data(1024);
297 for (size_t i = 0; i < data.size(); ++i)
298 data[i] = i % UINT8_MAX;
299 ASSERT_EQ(data.size(),
300 fwrite(&data.at(0), sizeof(uint8_t), data.size(), file.get()));
301 }
302 ASSERT_TRUE(PathExists(file_path));
303
304 // Validate collection returns nullptr.
305 PostmortemReportCollector collector;
306 ASSERT_EQ(nullptr, collector.Collect(file_path));
307 }
308
309 namespace {
310
311 // Parameters for the activity tracking.
312 const size_t kFileSize = 2 * 1024;
313 const int kStackDepth = 4;
314 const uint64_t kAllocatorId = 0;
315 const char kAllocatorName[] = "PostmortemReportCollectorCollectionTest";
316
317 } // namespace
318
319 class PostmortemReportCollectorCollectionTest : public testing::Test {
320 public:
321 // Create a proper debug file.
322 void SetUp() override {
323 testing::Test::SetUp();
324
325 // Create a file backed allocator.
326 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
327 debug_file_path_ = temp_dir_.path().AppendASCII("debug_file.pma");
328 std::unique_ptr<PersistentMemoryAllocator> allocator = CreateAllocator();
329 ASSERT_NE(nullptr, allocator);
330
331 size_t tracker_mem_size =
332 ThreadActivityTracker::SizeForStackDepth(kStackDepth);
333 ASSERT_GT(kFileSize, tracker_mem_size);
334
335 // Create some debug data using trackers.
336 std::unique_ptr<ThreadActivityTracker> tracker =
337 CreateTracker(allocator.get(), tracker_mem_size);
338 ASSERT_NE(nullptr, tracker);
339 ASSERT_TRUE(tracker->IsValid());
340
341 const void* dummy_task_origin = reinterpret_cast<void*>(0xCAFE);
342 const int dummy_task_sequence_num = 42;
343 tracker->PushActivity(dummy_task_origin, Activity::ACT_TASK_RUN,
344 ActivityData::ForTask(dummy_task_sequence_num));
345
346 // TODO(manzagop): flesh out the data (more trackers and content).
347 }
348
349 std::unique_ptr<PersistentMemoryAllocator> CreateAllocator() {
350 // Create the memory mapped file.
351 std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
352 bool success = mmfile->Initialize(
353 File(debug_file_path_, File::FLAG_CREATE | File::FLAG_READ |
354 File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
355 {0, static_cast<int64_t>(kFileSize)},
356 MemoryMappedFile::READ_WRITE_EXTEND);
357 if (!success || !mmfile->IsValid())
358 return nullptr;
359
360 // Create a persistent memory allocator.
361 if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true))
362 return nullptr;
363 return WrapUnique(new FilePersistentMemoryAllocator(
364 std::move(mmfile), kFileSize, kAllocatorId, kAllocatorName, false));
365 }
366
367 std::unique_ptr<ThreadActivityTracker> CreateTracker(
368 PersistentMemoryAllocator* allocator,
369 size_t tracker_mem_size) {
370 // Allocate a block of memory for the tracker to use.
371 PersistentMemoryAllocator::Reference mem_reference = allocator->Allocate(
372 tracker_mem_size, GlobalActivityTracker::kTypeIdActivityTracker);
373 if (mem_reference == 0U)
374 return nullptr;
375
376 // Get the memory's base address.
377 void* mem_base = allocator->GetAsObject<char>(
378 mem_reference, GlobalActivityTracker::kTypeIdActivityTracker);
379 if (mem_base == nullptr)
380 return nullptr;
381
382 // Make the allocation iterable so it can be found by other processes.
383 allocator->MakeIterable(mem_reference);
384
385 return WrapUnique(new ThreadActivityTracker(mem_base, tracker_mem_size));
386 }
387
388 const base::FilePath& debug_file_path() const { return debug_file_path_; }
389
390 private:
391 base::ScopedTempDir temp_dir_;
392 base::FilePath debug_file_path_;
393 };
394
395 TEST_F(PostmortemReportCollectorCollectionTest, CollectSuccess) {
396 // Validate collection returns the expected report.
397 PostmortemReportCollector collector;
398 std::unique_ptr<StabilityReport> report =
399 collector.Collect(debug_file_path());
400 ASSERT_NE(nullptr, report);
401
402 // Build the expected report.
403 StabilityReport expected_report;
404 ProcessState* process_state = expected_report.add_process_states();
405 ThreadState* thread_state = process_state->add_threads();
406 thread_state->set_thread_name(base::PlatformThread::GetName());
407
408 ASSERT_EQ(expected_report.SerializeAsString(), report->SerializeAsString());
409 }
410
411 } // namespace browser_watcher
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698