1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gmock/gmock.h>
18 #include <gtest/gtest.h>
19
20 #include <fcntl.h>
21 #include <libgen.h>
22
23 #include <android-base/file.h>
24 #include <android/os/BnDumpstate.h>
25 #include <android/os/BnDumpstateListener.h>
26 #include <binder/IServiceManager.h>
27 #include <binder/ProcessState.h>
28 #include <cutils/properties.h>
29 #include <ziparchive/zip_archive.h>
30
31 #include "dumpstate.h"
32
33 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
34
35 namespace android {
36 namespace os {
37 namespace dumpstate {
38
39 using ::testing::Test;
40 using ::std::literals::chrono_literals::operator""s;
41 using android::base::unique_fd;
42
43 class DumpstateListener;
44
45 namespace {
46
GetDumpstateService()47 sp<IDumpstate> GetDumpstateService() {
48 return android::interface_cast<IDumpstate>(
49 android::defaultServiceManager()->getService(String16("dumpstate")));
50 }
51
OpenForWrite(const std::string & filename)52 int OpenForWrite(const std::string& filename) {
53 return TEMP_FAILURE_RETRY(open(filename.c_str(),
54 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
55 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
56 }
57
58 } // namespace
59
60 struct SectionInfo {
61 std::string name;
62 status_t status;
63 int32_t size_bytes;
64 int32_t duration_ms;
65 };
66
67 /**
68 * Listens to bugreport progress and updates the user by writing the progress to STDOUT. All the
69 * section details generated by dumpstate are added to a vector to be used by Tests later.
70 */
71 class DumpstateListener : public BnDumpstateListener {
72 public:
DumpstateListener(int fd,std::shared_ptr<std::vector<SectionInfo>> sections)73 DumpstateListener(int fd, std::shared_ptr<std::vector<SectionInfo>> sections)
74 : out_fd_(fd), sections_(sections) {
75 }
76
DumpstateListener(int fd)77 DumpstateListener(int fd) : out_fd_(fd) {
78 }
79
onProgress(int32_t progress)80 binder::Status onProgress(int32_t progress) override {
81 dprintf(out_fd_, "\rIn progress %d", progress);
82 return binder::Status::ok();
83 }
84
onError(int32_t error_code)85 binder::Status onError(int32_t error_code) override {
86 std::lock_guard<std::mutex> lock(lock_);
87 error_code_ = error_code;
88 dprintf(out_fd_, "\rError code %d", error_code);
89 return binder::Status::ok();
90 }
91
onFinished()92 binder::Status onFinished() override {
93 std::lock_guard<std::mutex> lock(lock_);
94 is_finished_ = true;
95 dprintf(out_fd_, "\rFinished");
96 return binder::Status::ok();
97 }
98
onProgressUpdated(int32_t progress)99 binder::Status onProgressUpdated(int32_t progress) override {
100 dprintf(out_fd_, "\rIn progress %d/%d", progress, max_progress_);
101 return binder::Status::ok();
102 }
103
onMaxProgressUpdated(int32_t max_progress)104 binder::Status onMaxProgressUpdated(int32_t max_progress) override {
105 std::lock_guard<std::mutex> lock(lock_);
106 max_progress_ = max_progress;
107 return binder::Status::ok();
108 }
109
onSectionComplete(const::std::string & name,int32_t status,int32_t size_bytes,int32_t duration_ms)110 binder::Status onSectionComplete(const ::std::string& name, int32_t status, int32_t size_bytes,
111 int32_t duration_ms) override {
112 std::lock_guard<std::mutex> lock(lock_);
113 if (sections_.get() != nullptr) {
114 sections_->push_back({name, status, size_bytes, duration_ms});
115 }
116 return binder::Status::ok();
117 }
118
getIsFinished()119 bool getIsFinished() {
120 std::lock_guard<std::mutex> lock(lock_);
121 return is_finished_;
122 }
123
getErrorCode()124 int getErrorCode() {
125 std::lock_guard<std::mutex> lock(lock_);
126 return error_code_;
127 }
128
129 private:
130 int out_fd_;
131 int max_progress_ = 5000;
132 int error_code_ = -1;
133 bool is_finished_ = false;
134 std::shared_ptr<std::vector<SectionInfo>> sections_;
135 std::mutex lock_;
136 };
137
138 /**
139 * Generates bug report and provide access to the bug report file and other info for other tests.
140 * Since bug report generation is slow, the bugreport is only generated once.
141 */
142 class ZippedBugreportGenerationTest : public Test {
143 public:
144 static std::shared_ptr<std::vector<SectionInfo>> sections;
145 static Dumpstate& ds;
146 static std::chrono::milliseconds duration;
SetUpTestCase()147 static void SetUpTestCase() {
148 property_set("dumpstate.options", "bugreportplus");
149 // clang-format off
150 char* argv[] = {
151 (char*)"dumpstate",
152 (char*)"-d",
153 (char*)"-z",
154 (char*)"-B",
155 (char*)"-o",
156 (char*)dirname(android::base::GetExecutablePath().c_str())
157 };
158 // clang-format on
159 sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout)), sections));
160 ds.listener_ = listener;
161 ds.listener_name_ = "Smokey";
162 ds.report_section_ = true;
163 auto start = std::chrono::steady_clock::now();
164 ds.ParseCommandlineAndRun(ARRAY_SIZE(argv), argv);
165 auto end = std::chrono::steady_clock::now();
166 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
167 }
168
getZipFilePath()169 static const char* getZipFilePath() {
170 return ds.GetPath(".zip").c_str();
171 }
172 };
173 std::shared_ptr<std::vector<SectionInfo>> ZippedBugreportGenerationTest::sections =
174 std::make_shared<std::vector<SectionInfo>>();
175 Dumpstate& ZippedBugreportGenerationTest::ds = Dumpstate::GetInstance();
176 std::chrono::milliseconds ZippedBugreportGenerationTest::duration = 0s;
177
TEST_F(ZippedBugreportGenerationTest,IsGeneratedWithoutErrors)178 TEST_F(ZippedBugreportGenerationTest, IsGeneratedWithoutErrors) {
179 EXPECT_EQ(access(getZipFilePath(), F_OK), 0);
180 }
181
TEST_F(ZippedBugreportGenerationTest,Is3MBto30MBinSize)182 TEST_F(ZippedBugreportGenerationTest, Is3MBto30MBinSize) {
183 struct stat st;
184 EXPECT_EQ(stat(getZipFilePath(), &st), 0);
185 EXPECT_GE(st.st_size, 3000000 /* 3MB */);
186 EXPECT_LE(st.st_size, 30000000 /* 30MB */);
187 }
188
TEST_F(ZippedBugreportGenerationTest,TakesBetween30And150Seconds)189 TEST_F(ZippedBugreportGenerationTest, TakesBetween30And150Seconds) {
190 EXPECT_GE(duration, 30s) << "Expected completion in more than 30s. Actual time "
191 << duration.count() << " s.";
192 EXPECT_LE(duration, 150s) << "Expected completion in less than 150s. Actual time "
193 << duration.count() << " s.";
194 }
195
196 /**
197 * Run tests on contents of zipped bug report.
198 */
199 class ZippedBugReportContentsTest : public Test {
200 public:
201 ZipArchiveHandle handle;
SetUp()202 void SetUp() {
203 ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath(), &handle), 0);
204 }
TearDown()205 void TearDown() {
206 CloseArchive(handle);
207 }
208
FileExists(const char * filename,uint32_t minsize,uint32_t maxsize)209 void FileExists(const char* filename, uint32_t minsize, uint32_t maxsize) {
210 ZipEntry entry;
211 EXPECT_EQ(FindEntry(handle, ZipString(filename), &entry), 0);
212 EXPECT_GT(entry.uncompressed_length, minsize);
213 EXPECT_LT(entry.uncompressed_length, maxsize);
214 }
215 };
216
TEST_F(ZippedBugReportContentsTest,ContainsMainEntry)217 TEST_F(ZippedBugReportContentsTest, ContainsMainEntry) {
218 ZipEntry mainEntryLoc;
219 // contains main entry name file
220 EXPECT_EQ(FindEntry(handle, ZipString("main_entry.txt"), &mainEntryLoc), 0);
221
222 char* buf = new char[mainEntryLoc.uncompressed_length];
223 ExtractToMemory(handle, &mainEntryLoc, (uint8_t*)buf, mainEntryLoc.uncompressed_length);
224 delete[] buf;
225
226 // contains main entry file
227 FileExists(buf, 1000000U, 50000000U);
228 }
229
TEST_F(ZippedBugReportContentsTest,ContainsVersion)230 TEST_F(ZippedBugReportContentsTest, ContainsVersion) {
231 ZipEntry entry;
232 // contains main entry name file
233 EXPECT_EQ(FindEntry(handle, ZipString("version.txt"), &entry), 0);
234
235 char* buf = new char[entry.uncompressed_length + 1];
236 ExtractToMemory(handle, &entry, (uint8_t*)buf, entry.uncompressed_length);
237 buf[entry.uncompressed_length] = 0;
238 EXPECT_STREQ(buf, ZippedBugreportGenerationTest::ds.version_.c_str());
239 delete[] buf;
240 }
241
TEST_F(ZippedBugReportContentsTest,ContainsBoardSpecificFiles)242 TEST_F(ZippedBugReportContentsTest, ContainsBoardSpecificFiles) {
243 FileExists("dumpstate_board.bin", 1000000U, 80000000U);
244 FileExists("dumpstate_board.txt", 100000U, 1000000U);
245 }
246
247 // Spot check on some files pulled from the file system
TEST_F(ZippedBugReportContentsTest,ContainsSomeFileSystemFiles)248 TEST_F(ZippedBugReportContentsTest, ContainsSomeFileSystemFiles) {
249 // FS/proc/*/mountinfo size > 0
250 FileExists("FS/proc/1/mountinfo", 0U, 100000U);
251
252 // FS/data/misc/profiles/cur/0/*/primary.prof size > 0
253 FileExists("FS/data/misc/profiles/cur/0/com.android.phone/primary.prof", 0U, 100000U);
254 }
255
256 /**
257 * Runs tests on section data generated by dumpstate and captured by DumpstateListener.
258 */
259 class BugreportSectionTest : public Test {
260 public:
numMatches(const std::string & substring)261 int numMatches(const std::string& substring) {
262 int matches = 0;
263 for (auto const& section : *ZippedBugreportGenerationTest::sections) {
264 if (section.name.find(substring) != std::string::npos) {
265 matches++;
266 }
267 }
268 return matches;
269 }
SectionExists(const std::string & sectionName,int minsize)270 void SectionExists(const std::string& sectionName, int minsize) {
271 for (auto const& section : *ZippedBugreportGenerationTest::sections) {
272 if (sectionName == section.name) {
273 EXPECT_GE(section.size_bytes, minsize);
274 return;
275 }
276 }
277 FAIL() << sectionName << " not found.";
278 }
279 };
280
281 // Test all sections are generated without timeouts or errors
TEST_F(BugreportSectionTest,GeneratedWithoutErrors)282 TEST_F(BugreportSectionTest, GeneratedWithoutErrors) {
283 for (auto const& section : *ZippedBugreportGenerationTest::sections) {
284 EXPECT_EQ(section.status, 0) << section.name << " failed with status " << section.status;
285 }
286 }
287
TEST_F(BugreportSectionTest,Atleast3CriticalDumpsysSectionsGenerated)288 TEST_F(BugreportSectionTest, Atleast3CriticalDumpsysSectionsGenerated) {
289 int numSections = numMatches("DUMPSYS CRITICAL");
290 EXPECT_GE(numSections, 3);
291 }
292
TEST_F(BugreportSectionTest,Atleast2HighDumpsysSectionsGenerated)293 TEST_F(BugreportSectionTest, Atleast2HighDumpsysSectionsGenerated) {
294 int numSections = numMatches("DUMPSYS HIGH");
295 EXPECT_GE(numSections, 2);
296 }
297
TEST_F(BugreportSectionTest,Atleast50NormalDumpsysSectionsGenerated)298 TEST_F(BugreportSectionTest, Atleast50NormalDumpsysSectionsGenerated) {
299 int allSections = numMatches("DUMPSYS");
300 int criticalSections = numMatches("DUMPSYS CRITICAL");
301 int highSections = numMatches("DUMPSYS HIGH");
302 int normalSections = allSections - criticalSections - highSections;
303
304 EXPECT_GE(normalSections, 50) << "Total sections less than 50 (Critical:" << criticalSections
305 << "High:" << highSections << "Normal:" << normalSections << ")";
306 }
307
TEST_F(BugreportSectionTest,Atleast1ProtoDumpsysSectionGenerated)308 TEST_F(BugreportSectionTest, Atleast1ProtoDumpsysSectionGenerated) {
309 int numSections = numMatches("proto/");
310 EXPECT_GE(numSections, 1);
311 }
312
313 // Test if some critical sections are being generated.
TEST_F(BugreportSectionTest,CriticalSurfaceFlingerSectionGenerated)314 TEST_F(BugreportSectionTest, CriticalSurfaceFlingerSectionGenerated) {
315 SectionExists("DUMPSYS CRITICAL - SurfaceFlinger", /* bytes= */ 10000);
316 }
317
TEST_F(BugreportSectionTest,ActivitySectionsGenerated)318 TEST_F(BugreportSectionTest, ActivitySectionsGenerated) {
319 SectionExists("DUMPSYS CRITICAL - activity", /* bytes= */ 5000);
320 SectionExists("DUMPSYS - activity", /* bytes= */ 10000);
321 }
322
TEST_F(BugreportSectionTest,CpuinfoSectionGenerated)323 TEST_F(BugreportSectionTest, CpuinfoSectionGenerated) {
324 SectionExists("DUMPSYS CRITICAL - cpuinfo", /* bytes= */ 1000);
325 }
326
TEST_F(BugreportSectionTest,WindowSectionGenerated)327 TEST_F(BugreportSectionTest, WindowSectionGenerated) {
328 SectionExists("DUMPSYS CRITICAL - window", /* bytes= */ 20000);
329 }
330
TEST_F(BugreportSectionTest,ConnectivitySectionsGenerated)331 TEST_F(BugreportSectionTest, ConnectivitySectionsGenerated) {
332 SectionExists("DUMPSYS HIGH - connectivity", /* bytes= */ 5000);
333 SectionExists("DUMPSYS - connectivity", /* bytes= */ 5000);
334 }
335
TEST_F(BugreportSectionTest,MeminfoSectionGenerated)336 TEST_F(BugreportSectionTest, MeminfoSectionGenerated) {
337 SectionExists("DUMPSYS HIGH - meminfo", /* bytes= */ 100000);
338 }
339
TEST_F(BugreportSectionTest,BatteryStatsSectionGenerated)340 TEST_F(BugreportSectionTest, BatteryStatsSectionGenerated) {
341 SectionExists("DUMPSYS - batterystats", /* bytes= */ 1000);
342 }
343
TEST_F(BugreportSectionTest,WifiSectionGenerated)344 TEST_F(BugreportSectionTest, WifiSectionGenerated) {
345 SectionExists("DUMPSYS - wifi", /* bytes= */ 100000);
346 }
347
348 class DumpstateBinderTest : public Test {
349 protected:
SetUp()350 void SetUp() override {
351 // In case there is a stray service, stop it first.
352 property_set("ctl.stop", "bugreportd");
353 // dry_run results in a faster bugreport.
354 property_set("dumpstate.dry_run", "true");
355 // We need to receive some async calls later. Ensure we have binder threads.
356 ProcessState::self()->startThreadPool();
357 }
358
TearDown()359 void TearDown() override {
360 property_set("ctl.stop", "bugreportd");
361 property_set("dumpstate.dry_run", "");
362
363 unlink("/data/local/tmp/tmp.zip");
364 unlink("/data/local/tmp/tmp.png");
365 }
366
367 // Waits until listener gets the callbacks.
WaitTillExecutionComplete(DumpstateListener * listener)368 void WaitTillExecutionComplete(DumpstateListener* listener) {
369 // Wait till one of finished, error or timeout.
370 static const int kBugreportTimeoutSeconds = 120;
371 int i = 0;
372 while (!listener->getIsFinished() && listener->getErrorCode() == -1 &&
373 i < kBugreportTimeoutSeconds) {
374 sleep(1);
375 i++;
376 }
377 }
378 };
379
TEST_F(DumpstateBinderTest,Baseline)380 TEST_F(DumpstateBinderTest, Baseline) {
381 // In the beginning dumpstate binder service is not running.
382 sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
383 EXPECT_EQ(ds_binder, nullptr);
384
385 // Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
386 // and makes it wait.
387 property_set("dumpstate.dry_run", "true");
388 property_set("ctl.start", "bugreportd");
389
390 // Now we are able to retrieve dumpstate binder service.
391 ds_binder = GetDumpstateService();
392 EXPECT_NE(ds_binder, nullptr);
393
394 // Prepare arguments
395 unique_fd bugreport_fd(OpenForWrite("/bugreports/tmp.zip"));
396 unique_fd screenshot_fd(OpenForWrite("/bugreports/tmp.png"));
397
398 EXPECT_NE(bugreport_fd.get(), -1);
399 EXPECT_NE(screenshot_fd.get(), -1);
400
401 sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
402 android::binder::Status status =
403 ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
404 Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener);
405 // startBugreport is an async call. Verify binder call succeeded first, then wait till listener
406 // gets expected callbacks.
407 EXPECT_TRUE(status.isOk());
408 WaitTillExecutionComplete(listener.get());
409
410 // Bugreport generation requires user consent, which we cannot get in a test set up,
411 // so instead of getting is_finished_, we are more likely to get a consent error.
412 EXPECT_TRUE(
413 listener->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_DENIED_CONSENT ||
414 listener->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
415
416 // The service should have died on its own, freeing itself up for a new invocation.
417 sleep(2);
418 ds_binder = GetDumpstateService();
419 EXPECT_EQ(ds_binder, nullptr);
420 }
421
TEST_F(DumpstateBinderTest,ServiceDies_OnInvalidInput)422 TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) {
423 // Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
424 // and makes it wait.
425 property_set("ctl.start", "bugreportd");
426 sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
427 EXPECT_NE(ds_binder, nullptr);
428
429 // Prepare arguments
430 unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
431 unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
432
433 EXPECT_NE(bugreport_fd.get(), -1);
434 EXPECT_NE(screenshot_fd.get(), -1);
435
436 // Call startBugreport with bad arguments.
437 sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
438 android::binder::Status status =
439 ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
440 2000, // invalid bugreport mode
441 listener);
442 EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
443
444 // The service should have died, freeing itself up for a new invocation.
445 sleep(2);
446 ds_binder = GetDumpstateService();
447 EXPECT_EQ(ds_binder, nullptr);
448 }
449
TEST_F(DumpstateBinderTest,SimultaneousBugreportsNotAllowed)450 TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) {
451 // Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
452 // and makes it wait.
453 property_set("dumpstate.dry_run", "true");
454 property_set("ctl.start", "bugreportd");
455 sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
456 EXPECT_NE(ds_binder, nullptr);
457
458 // Prepare arguments
459 unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
460 unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
461
462 EXPECT_NE(bugreport_fd.get(), -1);
463 EXPECT_NE(screenshot_fd.get(), -1);
464
465 sp<DumpstateListener> listener1(new DumpstateListener(dup(fileno(stdout))));
466 android::binder::Status status =
467 ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
468 Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener1);
469 EXPECT_TRUE(status.isOk());
470
471 // try to make another call to startBugreport. This should fail.
472 sp<DumpstateListener> listener2(new DumpstateListener(dup(fileno(stdout))));
473 status = ds_binder->startBugreport(123, "com.dummy.package", bugreport_fd, screenshot_fd,
474 Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener2);
475 EXPECT_FALSE(status.isOk());
476 WaitTillExecutionComplete(listener2.get());
477 EXPECT_EQ(listener2->getErrorCode(),
478 IDumpstateListener::BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
479
480 // Meanwhile the first call works as expected. Service should not die in this case.
481 WaitTillExecutionComplete(listener1.get());
482
483 // Bugreport generation requires user consent, which we cannot get in a test set up,
484 // so instead of getting is_finished_, we are more likely to get a consent error.
485 EXPECT_TRUE(
486 listener1->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_DENIED_CONSENT ||
487 listener1->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
488 }
489
490 } // namespace dumpstate
491 } // namespace os
492 } // namespace android
493