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

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

Issue 2339193003: A collector for postmortem reports (Closed)
Patch Set: 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 {
Sigurður Ásgeirsson 2016/09/14 18:04:31 ah, this is an interface - sweet!
manzagop (departed) 2016/09/15 15:07:38 Acknowledged.
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.
115 MATCHER_P(EqualsProto, message, "") {
116 std::string expected_serialized;
117 std::string actual_serialized;
118 message.SerializeToString(&expected_serialized);
119 arg.SerializeToString(&actual_serialized);
Sigurður Ásgeirsson 2016/09/14 18:04:31 is the serialized protobuf canonical? ISTR an upco
manzagop (departed) 2016/09/15 15:07:38 You're right, there is no deterministic iteration
120 return expected_serialized == actual_serialized;
121 }
122
123 } // namespace
124
125 TEST(PostmortemReportCollectorTest, CollectAndSubmitForUpload) {
Sigurður Ásgeirsson 2016/09/14 18:04:32 IMHO there ought to be a test for the "stuck file"
manzagop (departed) 2016/09/15 15:07:38 Done.
126 // Create a dummy debug file, to validate deletion.
127 base::ScopedTempDir temp_dir;
128 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
129 base::FilePath debug_file = temp_dir.path().AppendASCII("foo-1.pma");
130 {
131 base::ScopedFILE file(base::OpenFile(debug_file, "w"));
132 ASSERT_NE(file.get(), nullptr);
133 }
134 ASSERT_TRUE(base::PathExists(debug_file));
135 const base::FilePath::StringType debug_file_pattern(
136 FILE_PATH_LITERAL("foo-*.pma"));
137
138 // Create mocks.
139 // Note: the database is owned by the collector.
140 std::unique_ptr<MockCrashReportDatabase> database(
Sigurður Ásgeirsson 2016/09/14 18:04:31 nit: is there a reason why this can't be a plain-o
manzagop (departed) 2016/09/15 15:07:38 Done.
141 new MockCrashReportDatabase());
142 MockPostmortemReportCollector collector;
143
144 // Expect collection of the debug file paths.
145 std::set<base::FilePath> no_excluded_files;
146 std::vector<base::FilePath> debug_files{debug_file};
147
148 EXPECT_CALL(collector, GetDebugStateFilePaths(
149 debug_file.DirName(), debug_file_pattern,
150 no_excluded_files)) // debug_file.DirName(),
151 // FILE_PATH_LITERAL("foo-*.pma"),no_excluded_files))
152 .Times(1)
153 .WillOnce(Return(debug_files));
154
155 EXPECT_CALL(*database, GetSettings()).Times(1).WillOnce(Return(nullptr));
156
157 // Expect collection to a proto of a single debug file.
158 // Note: caller takes ownership.
159 StabilityReport* stability_report = new StabilityReport();
160 EXPECT_CALL(collector, CollectRaw(debug_file))
161 .Times(1)
162 .WillOnce(Return(stability_report));
163
164 // Expect the call to write the proto to a minidump. This involves requesting
165 // a report from the crashpad database, writing the report, then finalizing it
166 // with the database.
167 base::FilePath minidump_path = temp_dir.path().AppendASCII("foo-1.dmp");
168 base::File minidump_file(
169 minidump_path, base::File::FLAG_CREATE | base::File::File::FLAG_WRITE);
170 CrashReportDatabase::NewReport crashpad_report = {
171 minidump_file.GetPlatformFile(),
172 crashpad::UUID(UUID::InitializeWithNewTag{}), minidump_path};
173 EXPECT_CALL(*database, PrepareNewCrashReport(_))
174 .Times(1)
175 .WillOnce(DoAll(SetArgPointee<0>(&crashpad_report),
176 Return(CrashReportDatabase::kNoError)));
177
178 EXPECT_CALL(collector,
179 WriteReportToMinidump(EqualsProto(*stability_report), _, _,
180 minidump_file.GetPlatformFile()))
181 .Times(1)
182 .WillOnce(Return(true));
183
184 EXPECT_CALL(*database, FinishedWritingCrashReport(&crashpad_report, _))
185 .Times(1)
186 .WillOnce(Return(CrashReportDatabase::kNoError));
187
188 // Run the test.
189 int postmortem_cnt = collector.CollectAndSubmitForUpload(
190 debug_file.DirName(), debug_file_pattern, no_excluded_files,
191 database.get());
192 ASSERT_EQ(1, postmortem_cnt);
193 ASSERT_FALSE(base::PathExists(debug_file));
194 }
195
196 TEST(PostmortemReportCollectorTest, GetDebugStateFilePaths) {
197 base::ScopedTempDir temp_dir;
198 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
199
200 // Create files.
201 std::vector<base::FilePath> expected_paths;
202 std::set<base::FilePath> excluded_paths;
203 {
204 // Matches the pattern.
205 base::FilePath path = temp_dir.path().AppendASCII("foo1.pma");
206 base::ScopedFILE file(base::OpenFile(path, "w"));
207 ASSERT_NE(file.get(), nullptr);
208 expected_paths.push_back(path);
209
210 // Matches the pattern, but is excluded.
211 path = temp_dir.path().AppendASCII("foo2.pma");
212 file.reset(base::OpenFile(path, "w"));
213 ASSERT_NE(file.get(), nullptr);
214 ASSERT_TRUE(excluded_paths.insert(path).second);
215
216 // Matches the pattern.
217 path = temp_dir.path().AppendASCII("foo3.pma");
218 file.reset(base::OpenFile(path, "w"));
219 ASSERT_NE(file.get(), nullptr);
220 expected_paths.push_back(path);
221
222 // Does not match the pattern.
223 path = temp_dir.path().AppendASCII("bar.baz");
224 file.reset(base::OpenFile(path, "w"));
225 ASSERT_NE(file.get(), nullptr);
226 }
227
228 PostmortemReportCollector collector;
229 EXPECT_THAT(
230 collector.GetDebugStateFilePaths(
231 temp_dir.path(), FILE_PATH_LITERAL("foo*.pma"), excluded_paths),
232 testing::UnorderedElementsAreArray(expected_paths));
233 }
234
235 TEST(PostmortemReportCollectorTest, CollectEmptyFile) {
236 // Create an empty file.
237 base::ScopedTempDir temp_dir;
238 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
239 base::FilePath file_path = temp_dir.path().AppendASCII("empty.pma");
240 {
241 base::ScopedFILE file(base::OpenFile(file_path, "w"));
242 ASSERT_NE(file.get(), nullptr);
243 }
244 ASSERT_TRUE(PathExists(file_path));
245
246 // Validate collection returns nullptr.
247 PostmortemReportCollector collector;
248 ASSERT_EQ(nullptr, collector.Collect(file_path));
249 }
250
251 TEST(PostmortemReportCollectorTest, CollectRandomFile) {
252 // Create a file with content we don't expect to be valid for a debug file.
253 base::ScopedTempDir temp_dir;
254 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
255 base::FilePath file_path = temp_dir.path().AppendASCII("invalid_content.pma");
256 {
257 base::ScopedFILE file(base::OpenFile(file_path, "w"));
258 ASSERT_NE(file.get(), nullptr);
259 // Assuming this size is greater than the minimum size of a debug file.
260 std::vector<uint8_t> data(1024);
261 for (size_t i = 0; i < data.size(); ++i)
262 data[i] = i % UINT8_MAX;
263 ASSERT_EQ(data.size(),
264 fwrite(&data.at(0), sizeof(uint8_t), data.size(), file.get()));
265 }
266 ASSERT_TRUE(PathExists(file_path));
267
268 // Validate collection returns nullptr.
269 PostmortemReportCollector collector;
270 ASSERT_EQ(nullptr, collector.Collect(file_path));
271 }
272
273 namespace {
274
275 // Parameters for the activity tracking.
276 const size_t kFileSize = 2 * 1024;
277 const int kStackDepth = 4;
278 const uint64_t kAllocatorId = 0;
279 const char kAllocatorName[] = "PostmortemReportCollectorCollectionTest";
280
281 } // namespace
282
283 class PostmortemReportCollectorCollectionTest : public testing::Test {
284 public:
285 // Create a proper debug file.
286 void SetUp() override {
287 testing::Test::SetUp();
288
289 // Create a file backed allocator.
290 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
291 debug_file_path_ = temp_dir_.path().AppendASCII("debug_file.pma");
292 std::unique_ptr<PersistentMemoryAllocator> allocator = CreateAllocator();
293 ASSERT_NE(nullptr, allocator);
294
295 size_t tracker_mem_size =
296 ThreadActivityTracker::SizeForStackDepth(kStackDepth);
297 ASSERT_GT(kFileSize, tracker_mem_size);
298
299 // Create some debug data using trackers.
300 std::unique_ptr<ThreadActivityTracker> tracker =
301 CreateTracker(allocator.get(), tracker_mem_size);
302 ASSERT_NE(nullptr, tracker);
303 ASSERT_TRUE(tracker->IsValid());
304
305 const void* dummy_task_origin = reinterpret_cast<void*>(0xCAFE);
306 const int dummy_task_sequence_num = 42;
307 tracker->PushActivity(dummy_task_origin, Activity::ACT_TASK_RUN,
308 ActivityData::ForTask(dummy_task_sequence_num));
309
310 // TODO(manzagop): flesh out the data (more trackers and content).
311 }
312
313 std::unique_ptr<PersistentMemoryAllocator> CreateAllocator() {
314 // Create the memory mapped file.
315 std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
316 bool success = mmfile->Initialize(
317 File(debug_file_path_, File::FLAG_CREATE | File::FLAG_READ |
318 File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
319 {0, static_cast<int64_t>(kFileSize)},
320 MemoryMappedFile::READ_WRITE_EXTEND);
321 if (!success || !mmfile->IsValid())
322 return nullptr;
323
324 // Create a persistent memory allocator.
325 if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true))
326 return nullptr;
327 return WrapUnique(new FilePersistentMemoryAllocator(
328 std::move(mmfile), kFileSize, kAllocatorId, kAllocatorName, false));
329 }
330
331 std::unique_ptr<ThreadActivityTracker> CreateTracker(
332 PersistentMemoryAllocator* allocator,
333 size_t tracker_mem_size) {
334 // Allocate a block of memory for the tracker to use.
335 PersistentMemoryAllocator::Reference mem_reference = allocator->Allocate(
336 tracker_mem_size, GlobalActivityTracker::kTypeIdActivityTracker);
337 if (mem_reference == 0U)
338 return nullptr;
339
340 // Get the memory's base address.
341 void* mem_base = allocator->GetAsObject<char>(
342 mem_reference, GlobalActivityTracker::kTypeIdActivityTracker);
343 if (mem_base == nullptr)
344 return nullptr;
345
346 // Make the allocation iterable so it can be found by other processes.
347 allocator->MakeIterable(mem_reference);
348
349 return WrapUnique(new ThreadActivityTracker(mem_base, tracker_mem_size));
350 }
351
352 const base::FilePath& debug_file_path() const { return debug_file_path_; }
353
354 private:
355 base::ScopedTempDir temp_dir_;
356 base::FilePath debug_file_path_;
357 };
358
359 TEST_F(PostmortemReportCollectorCollectionTest, CollectSuccess) {
360 // Validate collection returns the expected report.
361 PostmortemReportCollector collector;
362 std::unique_ptr<StabilityReport> report =
363 collector.Collect(debug_file_path());
364 ASSERT_NE(nullptr, report);
365
366 // Build the expected report.
367 StabilityReport expected_report;
368 ProcessState* process_state = expected_report.add_process_states();
369 ThreadState* thread_state = process_state->add_threads();
370 thread_state->set_thread_name(base::PlatformThread::GetName());
371
372 ASSERT_EQ(expected_report.SerializeAsString(), report->SerializeAsString());
373 }
374
375 } // namespace browser_watcher
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698