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 <android-base/file.h>
18 #include <android/os/BnDumpstate.h>
19 #include <android/os/BnDumpstateListener.h>
20 #include <binder/IServiceManager.h>
21 #include <binder/ProcessState.h>
22 #include <cutils/properties.h>
23 #include <fcntl.h>
24 #include <gmock/gmock.h>
25 #include <gtest/gtest.h>
26 #include <libgen.h>
27 #include <signal.h>
28 #include <ziparchive/zip_archive.h>
29
30 #include <cstdio>
31 #include <fstream>
32 #include <regex>
33
34 #include "dumpstate.h"
35
36 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
37
38 namespace android {
39 namespace os {
40 namespace dumpstate {
41
42 using ::testing::Test;
43 using ::std::literals::chrono_literals::operator""s;
44 using android::base::unique_fd;
45
46 class DumpstateListener;
47
48 namespace {
49
50 struct SectionInfo {
51 std::string name;
52 int32_t size_bytes;
53 };
54
GetDumpstateService()55 sp<IDumpstate> GetDumpstateService() {
56 return android::interface_cast<IDumpstate>(
57 android::defaultServiceManager()->getService(String16("dumpstate")));
58 }
59
OpenForWrite(const std::string & filename)60 int OpenForWrite(const std::string& filename) {
61 return TEMP_FAILURE_RETRY(open(filename.c_str(),
62 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
63 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
64 }
65
GetEntry(const ZipArchiveHandle archive,const std::string_view entry_name,ZipEntry * data)66 void GetEntry(const ZipArchiveHandle archive, const std::string_view entry_name, ZipEntry* data) {
67 int32_t e = FindEntry(archive, entry_name, data);
68 EXPECT_EQ(e, 0) << ErrorCodeString(e) << " entry name: " << entry_name;
69 }
70
71 // Extracts the main bugreport txt from the given archive and writes into output_fd.
ExtractBugreport(const ZipArchiveHandle * handle,int output_fd)72 void ExtractBugreport(const ZipArchiveHandle* handle, int output_fd) {
73 // Read contents of main_entry.txt which is a single line indicating the name of the zip entry
74 // that contains the main bugreport txt.
75 ZipEntry main_entry;
76 GetEntry(*handle, "main_entry.txt", &main_entry);
77 std::string bugreport_txt_name;
78 bugreport_txt_name.resize(main_entry.uncompressed_length);
79 ExtractToMemory(*handle, &main_entry, reinterpret_cast<uint8_t*>(bugreport_txt_name.data()),
80 main_entry.uncompressed_length);
81
82 // Read the main bugreport txt and extract to output_fd.
83 ZipEntry entry;
84 GetEntry(*handle, bugreport_txt_name, &entry);
85 ExtractEntryToFile(*handle, &entry, output_fd);
86 }
87
IsSectionStart(const std::string & line,std::string * section_name)88 bool IsSectionStart(const std::string& line, std::string* section_name) {
89 static const std::regex kSectionStart = std::regex{"DUMP OF SERVICE (.*):"};
90 std::smatch match;
91 if (std::regex_match(line, match, kSectionStart)) {
92 *section_name = match.str(1);
93 return true;
94 }
95 return false;
96 }
97
IsSectionEnd(const std::string & line)98 bool IsSectionEnd(const std::string& line) {
99 // Not all lines that contain "was the duration of" is a section end, but all section ends do
100 // contain "was the duration of". The disambiguation can be done by the caller.
101 return (line.find("was the duration of") != std::string::npos);
102 }
103
104 // Extracts the zipped bugreport and identifies the sections.
ParseSections(const std::string & zip_path,std::vector<SectionInfo> * sections)105 void ParseSections(const std::string& zip_path, std::vector<SectionInfo>* sections) {
106 // Open the archive
107 ZipArchiveHandle handle;
108 ASSERT_EQ(OpenArchive(zip_path.c_str(), &handle), 0);
109
110 // Extract the main entry to a temp file
111 TemporaryFile tmp_binary;
112 ASSERT_NE(-1, tmp_binary.fd);
113 ExtractBugreport(&handle, tmp_binary.fd);
114
115 // Read line by line and identify sections
116 std::ifstream ifs(tmp_binary.path, std::ifstream::in);
117 std::string line;
118 int section_bytes = 0;
119 std::string current_section_name;
120 while (std::getline(ifs, line)) {
121 std::string section_name;
122 if (IsSectionStart(line, §ion_name)) {
123 section_bytes = 0;
124 current_section_name = section_name;
125 } else if (IsSectionEnd(line)) {
126 if (!current_section_name.empty()) {
127 sections->push_back({current_section_name, section_bytes});
128 }
129 current_section_name = "";
130 } else if (!current_section_name.empty()) {
131 section_bytes += line.length();
132 }
133 }
134
135 CloseArchive(handle);
136 }
137
138 } // namespace
139
140 /**
141 * Listens to bugreport progress and updates the user by writing the progress to STDOUT. All the
142 * section details generated by dumpstate are added to a vector to be used by Tests later.
143 */
144 class DumpstateListener : public BnDumpstateListener {
145 public:
DumpstateListener(int fd,std::shared_ptr<std::vector<SectionInfo>> sections)146 DumpstateListener(int fd, std::shared_ptr<std::vector<SectionInfo>> sections)
147 : out_fd_(fd), sections_(sections) {
148 }
149
DumpstateListener(int fd)150 DumpstateListener(int fd) : out_fd_(fd) {
151 }
152
onProgress(int32_t progress)153 binder::Status onProgress(int32_t progress) override {
154 dprintf(out_fd_, "\rIn progress %d", progress);
155 return binder::Status::ok();
156 }
157
onError(int32_t error_code)158 binder::Status onError(int32_t error_code) override {
159 std::lock_guard<std::mutex> lock(lock_);
160 error_code_ = error_code;
161 dprintf(out_fd_, "\rError code %d", error_code);
162 return binder::Status::ok();
163 }
164
onFinished(const std::string & bugreport_file)165 binder::Status onFinished([[maybe_unused]] const std::string& bugreport_file) override {
166 std::lock_guard<std::mutex> lock(lock_);
167 is_finished_ = true;
168 dprintf(out_fd_, "\rFinished");
169 return binder::Status::ok();
170 }
171
onScreenshotTaken(bool success)172 binder::Status onScreenshotTaken(bool success) override {
173 std::lock_guard<std::mutex> lock(lock_);
174 dprintf(out_fd_, "\rResult of taking screenshot: %s", success ? "success" : "failure");
175 return binder::Status::ok();
176 }
177
onUiIntensiveBugreportDumpsFinished()178 binder::Status onUiIntensiveBugreportDumpsFinished() override {
179 std::lock_guard <std::mutex> lock(lock_);
180 dprintf(out_fd_, "\rUi intensive bugreport dumps finished");
181 return binder::Status::ok();
182 }
183
getIsFinished()184 bool getIsFinished() {
185 std::lock_guard<std::mutex> lock(lock_);
186 return is_finished_;
187 }
188
getErrorCode()189 int getErrorCode() {
190 std::lock_guard<std::mutex> lock(lock_);
191 return error_code_;
192 }
193
194 private:
195 int out_fd_;
196 int error_code_ = -1;
197 bool is_finished_ = false;
198 std::shared_ptr<std::vector<SectionInfo>> sections_;
199 std::mutex lock_;
200 };
201
202 /**
203 * Generates bug report and provide access to the bug report file and other info for other tests.
204 * Since bug report generation is slow, the bugreport is only generated once.
205 */
206 class ZippedBugreportGenerationTest : public Test {
207 public:
208 static std::shared_ptr<std::vector<SectionInfo>> sections;
209 static Dumpstate& ds;
210 static std::chrono::milliseconds duration;
GenerateBugreport()211 static void GenerateBugreport() {
212 // clang-format off
213 char* argv[] = {
214 (char*)"dumpstate"
215 };
216 // clang-format on
217 sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout)), sections));
218 ds.listener_ = listener;
219 auto start = std::chrono::steady_clock::now();
220 ds.ParseCommandlineAndRun(ARRAY_SIZE(argv), argv);
221 auto end = std::chrono::steady_clock::now();
222 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
223 }
224
getZipFilePath()225 static const std::string getZipFilePath() {
226 return ds.GetPath(".zip");
227 }
228 };
229 std::shared_ptr<std::vector<SectionInfo>> ZippedBugreportGenerationTest::sections =
230 std::make_shared<std::vector<SectionInfo>>();
231 Dumpstate& ZippedBugreportGenerationTest::ds = Dumpstate::GetInstance();
232 std::chrono::milliseconds ZippedBugreportGenerationTest::duration = 0s;
233
TEST_F(ZippedBugreportGenerationTest,IsGeneratedWithoutErrors)234 TEST_F(ZippedBugreportGenerationTest, IsGeneratedWithoutErrors) {
235 GenerateBugreport();
236 EXPECT_EQ(access(getZipFilePath().c_str(), F_OK), 0);
237 }
238
TEST_F(ZippedBugreportGenerationTest,Is1MBMBinSize)239 TEST_F(ZippedBugreportGenerationTest, Is1MBMBinSize) {
240 struct stat st;
241 EXPECT_EQ(stat(getZipFilePath().c_str(), &st), 0);
242 EXPECT_GE(st.st_size, 1000000 /* 1MB */);
243 }
244
TEST_F(ZippedBugreportGenerationTest,TakesBetween20And300Seconds)245 TEST_F(ZippedBugreportGenerationTest, TakesBetween20And300Seconds) {
246 EXPECT_GE(duration, 20s) << "Expected completion in more than 20s. Actual time "
247 << duration.count() << " ms.";
248 EXPECT_LE(duration, 300s) << "Expected completion in less than 300s. Actual time "
249 << duration.count() << " ms.";
250 }
251
252 /**
253 * Run tests on contents of zipped bug report.
254 */
255 class ZippedBugReportContentsTest : public Test {
256 public:
257 ZipArchiveHandle handle;
SetUp()258 void SetUp() {
259 ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath().c_str(), &handle), 0);
260 }
TearDown()261 void TearDown() {
262 CloseArchive(handle);
263 }
264
FileExists(const char * filename,uint32_t minsize,uint32_t maxsize=std::numeric_limits<uint32_t>::max ())265 void FileExists(const char* filename, uint32_t minsize,
266 uint32_t maxsize = std::numeric_limits<uint32_t>::max()) {
267 ZipEntry entry;
268 GetEntry(handle, filename, &entry);
269 EXPECT_GT(entry.uncompressed_length, minsize);
270 EXPECT_LT(entry.uncompressed_length, maxsize);
271 }
272 };
273
TEST_F(ZippedBugReportContentsTest,ContainsMainEntry)274 TEST_F(ZippedBugReportContentsTest, ContainsMainEntry) {
275 ZipEntry main_entry;
276 // contains main entry name file
277 GetEntry(handle, "main_entry.txt", &main_entry);
278
279 std::string bugreport_txt_name;
280 bugreport_txt_name.resize(main_entry.uncompressed_length);
281 ExtractToMemory(handle, &main_entry, reinterpret_cast<uint8_t*>(bugreport_txt_name.data()),
282 main_entry.uncompressed_length);
283
284 // contains main entry file
285 FileExists(bugreport_txt_name.c_str(), 1000000U);
286 }
287
TEST_F(ZippedBugReportContentsTest,ContainsVersion)288 TEST_F(ZippedBugReportContentsTest, ContainsVersion) {
289 ZipEntry entry;
290 // contains main entry name file
291 GetEntry(handle, "version.txt", &entry);
292
293 char* buf = new char[entry.uncompressed_length + 1];
294 ExtractToMemory(handle, &entry, (uint8_t*)buf, entry.uncompressed_length);
295 buf[entry.uncompressed_length] = 0;
296 EXPECT_STREQ(buf, ZippedBugreportGenerationTest::ds.version_.c_str());
297 delete[] buf;
298 }
299
TEST_F(ZippedBugReportContentsTest,ContainsBoardSpecificFiles)300 TEST_F(ZippedBugReportContentsTest, ContainsBoardSpecificFiles) {
301 // TODO(b/160109027): cf_x86_phone-userdebug does not dump them.
302 // FileExists("dumpstate_board.bin", 1000000U, 80000000U);
303 // FileExists("dumpstate_board.txt", 100000U, 1000000U);
304 }
305
TEST_F(ZippedBugReportContentsTest,ContainsProtoFile)306 TEST_F(ZippedBugReportContentsTest, ContainsProtoFile) {
307 FileExists("proto/activity.proto", 100000U, 1000000U);
308 }
309
310 // Spot check on some files pulled from the file system
TEST_F(ZippedBugReportContentsTest,ContainsSomeFileSystemFiles)311 TEST_F(ZippedBugReportContentsTest, ContainsSomeFileSystemFiles) {
312 // FS/proc/*/mountinfo size > 0
313 FileExists("FS/proc/1/mountinfo", 0U, 100000U);
314
315 // FS/data/misc/profiles/cur/0/*/primary.prof should exist. Also, since dumpstate only adds
316 // profiles to the zip in the non-user build, a build checking is necessary here.
317 if (!PropertiesHelper::IsUserBuild()) {
318 ZipEntry entry;
319 GetEntry(handle, "FS/data/misc/profiles/cur/0/com.android.phone/primary.prof", &entry);
320 }
321 }
322
323 /**
324 * Runs tests on section data generated by dumpstate and captured by DumpstateListener.
325 */
326 class BugreportSectionTest : public Test {
327 public:
328 ZipArchiveHandle handle;
329
SetUp()330 void SetUp() {
331 ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath().c_str(), &handle), 0);
332 }
333
TearDown()334 void TearDown() {
335 CloseArchive(handle);
336 }
337
SetUpTestCase()338 static void SetUpTestCase() {
339 ParseSections(ZippedBugreportGenerationTest::getZipFilePath().c_str(),
340 ZippedBugreportGenerationTest::sections.get());
341 }
342
numMatches(const std::string & substring)343 int numMatches(const std::string& substring) {
344 int matches = 0;
345 for (auto const& section : *ZippedBugreportGenerationTest::sections) {
346 if (section.name.find(substring) != std::string::npos) {
347 matches++;
348 }
349 }
350 return matches;
351 }
352
SectionExists(const std::string & sectionName,int minsize)353 void SectionExists(const std::string& sectionName, int minsize) {
354 for (auto const& section : *ZippedBugreportGenerationTest::sections) {
355 if (sectionName == section.name) {
356 EXPECT_GE(section.size_bytes, minsize) << " for section:" << sectionName;
357 return;
358 }
359 }
360 FAIL() << sectionName << " not found.";
361 }
362
363 /**
364 * Whether or not the content of the section is injected by other commands.
365 */
IsContentInjectedByOthers(const std::string & line)366 bool IsContentInjectedByOthers(const std::string& line) {
367 // Command header such as `------ APP ACTIVITIES (/system/bin/dumpsys activity -v) ------`.
368 static const std::regex kCommandHeader = std::regex{"------ .+ \\(.+\\) ------"};
369 std::smatch match;
370 if (std::regex_match(line, match, kCommandHeader)) {
371 return true;
372 }
373 return false;
374 }
375 };
376
TEST_F(BugreportSectionTest,Atleast3CriticalDumpsysSectionsGenerated)377 TEST_F(BugreportSectionTest, Atleast3CriticalDumpsysSectionsGenerated) {
378 int numSections = numMatches("CRITICAL");
379 EXPECT_GE(numSections, 3);
380 }
381
TEST_F(BugreportSectionTest,Atleast2HighDumpsysSectionsGenerated)382 TEST_F(BugreportSectionTest, Atleast2HighDumpsysSectionsGenerated) {
383 int numSections = numMatches("HIGH");
384 EXPECT_GE(numSections, 2);
385 }
386
TEST_F(BugreportSectionTest,Atleast50NormalDumpsysSectionsGenerated)387 TEST_F(BugreportSectionTest, Atleast50NormalDumpsysSectionsGenerated) {
388 int allSections = ZippedBugreportGenerationTest::sections->size();
389 int criticalSections = numMatches("CRITICAL");
390 int highSections = numMatches("HIGH");
391 int normalSections = allSections - criticalSections - highSections;
392
393 EXPECT_GE(normalSections, 50) << "Total sections less than 50 (Critical:" << criticalSections
394 << "High:" << highSections << "Normal:" << normalSections << ")";
395 }
396
397 // Test if some critical sections are being generated.
TEST_F(BugreportSectionTest,CriticalSurfaceFlingerSectionGenerated)398 TEST_F(BugreportSectionTest, CriticalSurfaceFlingerSectionGenerated) {
399 SectionExists("CRITICAL SurfaceFlinger", /* bytes= */ 10000);
400 }
401
TEST_F(BugreportSectionTest,ActivitySectionsGenerated)402 TEST_F(BugreportSectionTest, ActivitySectionsGenerated) {
403 SectionExists("CRITICAL activity", /* bytes= */ 5000);
404 SectionExists("activity", /* bytes= */ 10000);
405 }
406
TEST_F(BugreportSectionTest,CpuinfoSectionGenerated)407 TEST_F(BugreportSectionTest, CpuinfoSectionGenerated) {
408 SectionExists("CRITICAL cpuinfo", /* bytes= */ 1000);
409 }
410
TEST_F(BugreportSectionTest,WindowSectionGenerated)411 TEST_F(BugreportSectionTest, WindowSectionGenerated) {
412 SectionExists("CRITICAL window", /* bytes= */ 20000);
413 }
414
TEST_F(BugreportSectionTest,ConnectivitySectionsGenerated)415 TEST_F(BugreportSectionTest, ConnectivitySectionsGenerated) {
416 SectionExists("connectivity", /* bytes= */ 5000);
417 }
418
TEST_F(BugreportSectionTest,MeminfoSectionGenerated)419 TEST_F(BugreportSectionTest, MeminfoSectionGenerated) {
420 SectionExists("HIGH meminfo", /* bytes= */ 100000);
421 }
422
TEST_F(BugreportSectionTest,BatteryStatsSectionGenerated)423 TEST_F(BugreportSectionTest, BatteryStatsSectionGenerated) {
424 SectionExists("batterystats", /* bytes= */ 1000);
425 }
426
TEST_F(BugreportSectionTest,DISABLED_WifiSectionGenerated)427 TEST_F(BugreportSectionTest, DISABLED_WifiSectionGenerated) {
428 SectionExists("wifi", /* bytes= */ 100000);
429 }
430
TEST_F(BugreportSectionTest,NoInjectedContentByOtherCommand)431 TEST_F(BugreportSectionTest, NoInjectedContentByOtherCommand) {
432 // Extract the main entry to a temp file
433 TemporaryFile tmp_binary;
434 ASSERT_NE(-1, tmp_binary.fd);
435 ExtractBugreport(&handle, tmp_binary.fd);
436
437 // Read line by line and identify sections
438 std::ifstream ifs(tmp_binary.path, std::ifstream::in);
439 std::string line;
440 std::string current_section_name;
441 while (std::getline(ifs, line)) {
442 std::string section_name;
443 if (IsSectionStart(line, §ion_name)) {
444 current_section_name = section_name;
445 } else if (IsSectionEnd(line)) {
446 current_section_name = "";
447 } else if (!current_section_name.empty()) {
448 EXPECT_FALSE(IsContentInjectedByOthers(line));
449 }
450 }
451 }
452
453 class DumpstateBinderTest : public Test {
454 protected:
SetUp()455 void SetUp() override {
456 // In case there is a stray service, stop it first.
457 property_set("ctl.stop", "bugreportd");
458 // dry_run results in a faster bugreport.
459 property_set("dumpstate.dry_run", "true");
460 // We need to receive some async calls later. Ensure we have binder threads.
461 ProcessState::self()->startThreadPool();
462 }
463
TearDown()464 void TearDown() override {
465 property_set("ctl.stop", "bugreportd");
466 property_set("dumpstate.dry_run", "");
467
468 unlink("/data/local/tmp/tmp.zip");
469 unlink("/data/local/tmp/tmp.png");
470 }
471
472 // Waits until listener gets the callbacks.
WaitTillExecutionComplete(DumpstateListener * listener)473 void WaitTillExecutionComplete(DumpstateListener* listener) {
474 // Wait till one of finished, error or timeout.
475 static const int kBugreportTimeoutSeconds = 120;
476 int i = 0;
477 while (!listener->getIsFinished() && listener->getErrorCode() == -1 &&
478 i < kBugreportTimeoutSeconds) {
479 sleep(1);
480 i++;
481 }
482 }
483 };
484
TEST_F(DumpstateBinderTest,Baseline)485 TEST_F(DumpstateBinderTest, Baseline) {
486 // In the beginning dumpstate binder service is not running.
487 sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
488 EXPECT_EQ(ds_binder, nullptr);
489
490 // Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
491 // and makes it wait.
492 property_set("dumpstate.dry_run", "true");
493 property_set("ctl.start", "bugreportd");
494
495 // Now we are able to retrieve dumpstate binder service.
496 ds_binder = GetDumpstateService();
497 EXPECT_NE(ds_binder, nullptr);
498
499 // Prepare arguments
500 unique_fd bugreport_fd(OpenForWrite("/bugreports/tmp.zip"));
501 unique_fd screenshot_fd(OpenForWrite("/bugreports/tmp.png"));
502 int flags = 0;
503
504 EXPECT_NE(bugreport_fd.get(), -1);
505 EXPECT_NE(screenshot_fd.get(), -1);
506
507 sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
508 android::binder::Status status =
509 ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
510 std::move(screenshot_fd),
511 Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener,
512 true, false);
513 // startBugreport is an async call. Verify binder call succeeded first, then wait till listener
514 // gets expected callbacks.
515 EXPECT_TRUE(status.isOk());
516 WaitTillExecutionComplete(listener.get());
517
518 // Bugreport generation requires user consent, which we cannot get in a test set up,
519 // so instead of getting is_finished_, we are more likely to get a consent error.
520 EXPECT_TRUE(
521 listener->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_DENIED_CONSENT ||
522 listener->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
523
524 // The service should have died on its own, freeing itself up for a new invocation.
525 sleep(2);
526 ds_binder = GetDumpstateService();
527 EXPECT_EQ(ds_binder, nullptr);
528 }
529
TEST_F(DumpstateBinderTest,ServiceDies_OnInvalidInput)530 TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) {
531 // Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
532 // and makes it wait.
533 property_set("ctl.start", "bugreportd");
534 sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
535 EXPECT_NE(ds_binder, nullptr);
536
537 // Prepare arguments
538 unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
539 unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
540 int flags = 0;
541
542 EXPECT_NE(bugreport_fd.get(), -1);
543 EXPECT_NE(screenshot_fd.get(), -1);
544
545 // Call startBugreport with bad arguments.
546 sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
547 android::binder::Status status =
548 ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
549 std::move(screenshot_fd), 2000, // invalid bugreport mode
550 flags, listener, false, false);
551 EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
552
553 // The service should have died, freeing itself up for a new invocation.
554 sleep(2);
555 ds_binder = GetDumpstateService();
556 EXPECT_EQ(ds_binder, nullptr);
557 }
558
TEST_F(DumpstateBinderTest,SimultaneousBugreportsNotAllowed)559 TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) {
560 // Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
561 // and makes it wait.
562 property_set("dumpstate.dry_run", "true");
563 property_set("ctl.start", "bugreportd");
564 sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
565 EXPECT_NE(ds_binder, nullptr);
566
567 // Prepare arguments
568 unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
569 unique_fd bugreport_fd2(dup(bugreport_fd.get()));
570 unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
571 unique_fd screenshot_fd2(dup(screenshot_fd.get()));
572 int flags = 0;
573
574 EXPECT_NE(bugreport_fd.get(), -1);
575 EXPECT_NE(bugreport_fd2.get(), -1);
576 EXPECT_NE(screenshot_fd.get(), -1);
577 EXPECT_NE(screenshot_fd2.get(), -1);
578
579 sp<DumpstateListener> listener1(new DumpstateListener(dup(fileno(stdout))));
580 android::binder::Status status =
581 ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
582 std::move(screenshot_fd),
583 Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener1,
584 true, false);
585 EXPECT_TRUE(status.isOk());
586
587 // try to make another call to startBugreport. This should fail.
588 sp<DumpstateListener> listener2(new DumpstateListener(dup(fileno(stdout))));
589 status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd2),
590 std::move(screenshot_fd2),
591 Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags,
592 listener2, true, false);
593 EXPECT_FALSE(status.isOk());
594 WaitTillExecutionComplete(listener2.get());
595 EXPECT_EQ(listener2->getErrorCode(),
596 IDumpstateListener::BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
597
598 // Meanwhile the first call works as expected. Service should not die in this case.
599 WaitTillExecutionComplete(listener1.get());
600
601 // Bugreport generation requires user consent, which we cannot get in a test set up,
602 // so instead of getting is_finished_, we are more likely to get a consent error.
603 EXPECT_TRUE(
604 listener1->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_DENIED_CONSENT ||
605 listener1->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
606 }
607
608 class DumpstateTracingTest : public Test {
609 protected:
TearDown()610 void TearDown() override {
611 for (int pid : bg_process_pids) {
612 kill(pid, SIGKILL);
613 }
614 }
615
StartTracing(const std::string & config)616 void StartTracing(const std::string& config) {
617 // Write the perfetto config into a file.
618 const int id = static_cast<int>(bg_process_pids.size());
619 char cfg[64];
620 snprintf(cfg, sizeof(cfg), "/data/misc/perfetto-configs/br-%d", id);
621 unlink(cfg); // Remove the config file if it exists already.
622 FILE* f = fopen(cfg, "w");
623 ASSERT_NE(f, nullptr);
624 fputs(config.c_str(), f);
625 fclose(f);
626
627 // Invoke perfetto to start tracing.
628 char cmd[255];
629 snprintf(cmd, sizeof(cmd), "perfetto --background-wait --txt -o /dev/null -c %s", cfg);
630 FILE* proc = popen(cmd, "r");
631 ASSERT_NE(proc, nullptr);
632
633 // Read back the PID of the background process. We will use it to kill
634 // all tracing sessions when the test ends or fails.
635 char pid_str[32]{};
636 ASSERT_NE(fgets(pid_str, sizeof(pid_str), proc), nullptr);
637 int pid = atoi(pid_str);
638 bg_process_pids.push_back(pid);
639
640 pclose(proc);
641 unlink(cfg);
642 }
643
644 std::vector<int> bg_process_pids;
645 };
646
TEST_F(DumpstateTracingTest,ManyTracesInBugreport)647 TEST_F(DumpstateTracingTest, ManyTracesInBugreport) {
648 // Note the trace duration is irrelevant and is only an upper bound.
649 // Tracing is stopped as soon as the bugreport.zip creation ends.
650 StartTracing(R"(
651 buffers { size_kb: 4096 }
652 data_sources {
653 config {
654 name: "linux.ftrace"
655 }
656 }
657
658 duration_ms: 120000
659 bugreport_filename: "sys.pftrace"
660 bugreport_score: 100
661 )");
662
663 StartTracing(R"(
664 buffers { size_kb: 4096 }
665 data_sources {
666 config {
667 name: "linux.ftrace"
668 }
669 }
670
671 duration_ms: 120000
672 bugreport_score: 50
673 bugreport_filename: "mem.pftrace"
674 )");
675
676 ZippedBugreportGenerationTest::GenerateBugreport();
677 std::string zip_path = ZippedBugreportGenerationTest::getZipFilePath();
678 ZipArchiveHandle handle;
679 ASSERT_EQ(OpenArchive(zip_path.c_str(), &handle), 0);
680
681 const char* kExpectedEntries[]{
682 "FS/data/misc/perfetto-traces/bugreport/sys.pftrace",
683 "FS/data/misc/perfetto-traces/bugreport/mem.pftrace",
684 };
685
686 // Check that the bugreport contains both traces.
687 for (const char* file_path : kExpectedEntries) {
688 ZipEntry entry{};
689 GetEntry(handle, file_path, &entry);
690 EXPECT_GT(entry.uncompressed_length, 100);
691 }
692 CloseArchive(handle);
693 }
694
695 } // namespace dumpstate
696 } // namespace os
697 } // namespace android
698