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