600 lines
22 KiB
C++
600 lines
22 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <android-base/file.h>
|
|
#include <android/os/BnDumpstate.h>
|
|
#include <android/os/BnDumpstateListener.h>
|
|
#include <binder/IServiceManager.h>
|
|
#include <binder/ProcessState.h>
|
|
#include <cutils/properties.h>
|
|
#include <fcntl.h>
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
#include <libgen.h>
|
|
#include <ziparchive/zip_archive.h>
|
|
|
|
#include <fstream>
|
|
#include <regex>
|
|
|
|
#include "dumpstate.h"
|
|
|
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
|
|
|
namespace android {
|
|
namespace os {
|
|
namespace dumpstate {
|
|
|
|
using ::testing::Test;
|
|
using ::std::literals::chrono_literals::operator""s;
|
|
using android::base::unique_fd;
|
|
|
|
class DumpstateListener;
|
|
|
|
namespace {
|
|
|
|
struct SectionInfo {
|
|
std::string name;
|
|
int32_t size_bytes;
|
|
};
|
|
|
|
sp<IDumpstate> GetDumpstateService() {
|
|
return android::interface_cast<IDumpstate>(
|
|
android::defaultServiceManager()->getService(String16("dumpstate")));
|
|
}
|
|
|
|
int OpenForWrite(const std::string& filename) {
|
|
return TEMP_FAILURE_RETRY(open(filename.c_str(),
|
|
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
|
|
}
|
|
|
|
void GetEntry(const ZipArchiveHandle archive, const std::string_view entry_name, ZipEntry* data) {
|
|
int32_t e = FindEntry(archive, entry_name, data);
|
|
EXPECT_EQ(e, 0) << ErrorCodeString(e) << " entry name: " << entry_name;
|
|
}
|
|
|
|
// Extracts the main bugreport txt from the given archive and writes into output_fd.
|
|
void ExtractBugreport(const ZipArchiveHandle* handle, int output_fd) {
|
|
// Read contents of main_entry.txt which is a single line indicating the name of the zip entry
|
|
// that contains the main bugreport txt.
|
|
ZipEntry main_entry;
|
|
GetEntry(*handle, "main_entry.txt", &main_entry);
|
|
std::string bugreport_txt_name;
|
|
bugreport_txt_name.resize(main_entry.uncompressed_length);
|
|
ExtractToMemory(*handle, &main_entry, reinterpret_cast<uint8_t*>(bugreport_txt_name.data()),
|
|
main_entry.uncompressed_length);
|
|
|
|
// Read the main bugreport txt and extract to output_fd.
|
|
ZipEntry entry;
|
|
GetEntry(*handle, bugreport_txt_name, &entry);
|
|
ExtractEntryToFile(*handle, &entry, output_fd);
|
|
}
|
|
|
|
bool IsSectionStart(const std::string& line, std::string* section_name) {
|
|
static const std::regex kSectionStart = std::regex{"DUMP OF SERVICE (.*):"};
|
|
std::smatch match;
|
|
if (std::regex_match(line, match, kSectionStart)) {
|
|
*section_name = match.str(1);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IsSectionEnd(const std::string& line) {
|
|
// Not all lines that contain "was the duration of" is a section end, but all section ends do
|
|
// contain "was the duration of". The disambiguation can be done by the caller.
|
|
return (line.find("was the duration of") != std::string::npos);
|
|
}
|
|
|
|
// Extracts the zipped bugreport and identifies the sections.
|
|
void ParseSections(const std::string& zip_path, std::vector<SectionInfo>* sections) {
|
|
// Open the archive
|
|
ZipArchiveHandle handle;
|
|
ASSERT_EQ(OpenArchive(zip_path.c_str(), &handle), 0);
|
|
|
|
// Extract the main entry to a temp file
|
|
TemporaryFile tmp_binary;
|
|
ASSERT_NE(-1, tmp_binary.fd);
|
|
ExtractBugreport(&handle, tmp_binary.fd);
|
|
|
|
// Read line by line and identify sections
|
|
std::ifstream ifs(tmp_binary.path, std::ifstream::in);
|
|
std::string line;
|
|
int section_bytes = 0;
|
|
std::string current_section_name;
|
|
while (std::getline(ifs, line)) {
|
|
std::string section_name;
|
|
if (IsSectionStart(line, §ion_name)) {
|
|
section_bytes = 0;
|
|
current_section_name = section_name;
|
|
} else if (IsSectionEnd(line)) {
|
|
if (!current_section_name.empty()) {
|
|
sections->push_back({current_section_name, section_bytes});
|
|
}
|
|
current_section_name = "";
|
|
} else if (!current_section_name.empty()) {
|
|
section_bytes += line.length();
|
|
}
|
|
}
|
|
|
|
CloseArchive(handle);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/**
|
|
* Listens to bugreport progress and updates the user by writing the progress to STDOUT. All the
|
|
* section details generated by dumpstate are added to a vector to be used by Tests later.
|
|
*/
|
|
class DumpstateListener : public BnDumpstateListener {
|
|
public:
|
|
DumpstateListener(int fd, std::shared_ptr<std::vector<SectionInfo>> sections)
|
|
: out_fd_(fd), sections_(sections) {
|
|
}
|
|
|
|
DumpstateListener(int fd) : out_fd_(fd) {
|
|
}
|
|
|
|
binder::Status onProgress(int32_t progress) override {
|
|
dprintf(out_fd_, "\rIn progress %d", progress);
|
|
return binder::Status::ok();
|
|
}
|
|
|
|
binder::Status onError(int32_t error_code) override {
|
|
std::lock_guard<std::mutex> lock(lock_);
|
|
error_code_ = error_code;
|
|
dprintf(out_fd_, "\rError code %d", error_code);
|
|
return binder::Status::ok();
|
|
}
|
|
|
|
binder::Status onFinished() override {
|
|
std::lock_guard<std::mutex> lock(lock_);
|
|
is_finished_ = true;
|
|
dprintf(out_fd_, "\rFinished");
|
|
return binder::Status::ok();
|
|
}
|
|
|
|
binder::Status onScreenshotTaken(bool success) override {
|
|
std::lock_guard<std::mutex> lock(lock_);
|
|
dprintf(out_fd_, "\rResult of taking screenshot: %s", success ? "success" : "failure");
|
|
return binder::Status::ok();
|
|
}
|
|
|
|
binder::Status onUiIntensiveBugreportDumpsFinished() override {
|
|
std::lock_guard <std::mutex> lock(lock_);
|
|
dprintf(out_fd_, "\rUi intensive bugreport dumps finished");
|
|
return binder::Status::ok();
|
|
}
|
|
|
|
bool getIsFinished() {
|
|
std::lock_guard<std::mutex> lock(lock_);
|
|
return is_finished_;
|
|
}
|
|
|
|
int getErrorCode() {
|
|
std::lock_guard<std::mutex> lock(lock_);
|
|
return error_code_;
|
|
}
|
|
|
|
private:
|
|
int out_fd_;
|
|
int error_code_ = -1;
|
|
bool is_finished_ = false;
|
|
std::shared_ptr<std::vector<SectionInfo>> sections_;
|
|
std::mutex lock_;
|
|
};
|
|
|
|
/**
|
|
* Generates bug report and provide access to the bug report file and other info for other tests.
|
|
* Since bug report generation is slow, the bugreport is only generated once.
|
|
*/
|
|
class ZippedBugreportGenerationTest : public Test {
|
|
public:
|
|
static std::shared_ptr<std::vector<SectionInfo>> sections;
|
|
static Dumpstate& ds;
|
|
static std::chrono::milliseconds duration;
|
|
static void GenerateBugreport() {
|
|
// clang-format off
|
|
char* argv[] = {
|
|
(char*)"dumpstate"
|
|
};
|
|
// clang-format on
|
|
sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout)), sections));
|
|
ds.listener_ = listener;
|
|
auto start = std::chrono::steady_clock::now();
|
|
ds.ParseCommandlineAndRun(ARRAY_SIZE(argv), argv);
|
|
auto end = std::chrono::steady_clock::now();
|
|
duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
|
}
|
|
|
|
static const std::string getZipFilePath() {
|
|
return ds.GetPath(".zip");
|
|
}
|
|
};
|
|
std::shared_ptr<std::vector<SectionInfo>> ZippedBugreportGenerationTest::sections =
|
|
std::make_shared<std::vector<SectionInfo>>();
|
|
Dumpstate& ZippedBugreportGenerationTest::ds = Dumpstate::GetInstance();
|
|
std::chrono::milliseconds ZippedBugreportGenerationTest::duration = 0s;
|
|
|
|
TEST_F(ZippedBugreportGenerationTest, IsGeneratedWithoutErrors) {
|
|
GenerateBugreport();
|
|
EXPECT_EQ(access(getZipFilePath().c_str(), F_OK), 0);
|
|
}
|
|
|
|
TEST_F(ZippedBugreportGenerationTest, Is1MBMBinSize) {
|
|
struct stat st;
|
|
EXPECT_EQ(stat(getZipFilePath().c_str(), &st), 0);
|
|
EXPECT_GE(st.st_size, 1000000 /* 1MB */);
|
|
}
|
|
|
|
TEST_F(ZippedBugreportGenerationTest, TakesBetween20And300Seconds) {
|
|
EXPECT_GE(duration, 20s) << "Expected completion in more than 20s. Actual time "
|
|
<< duration.count() << " ms.";
|
|
EXPECT_LE(duration, 300s) << "Expected completion in less than 300s. Actual time "
|
|
<< duration.count() << " ms.";
|
|
}
|
|
|
|
/**
|
|
* Run tests on contents of zipped bug report.
|
|
*/
|
|
class ZippedBugReportContentsTest : public Test {
|
|
public:
|
|
ZipArchiveHandle handle;
|
|
void SetUp() {
|
|
ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath().c_str(), &handle), 0);
|
|
}
|
|
void TearDown() {
|
|
CloseArchive(handle);
|
|
}
|
|
|
|
void FileExists(const char* filename, uint32_t minsize,
|
|
uint32_t maxsize = std::numeric_limits<uint32_t>::max()) {
|
|
ZipEntry entry;
|
|
GetEntry(handle, filename, &entry);
|
|
EXPECT_GT(entry.uncompressed_length, minsize);
|
|
EXPECT_LT(entry.uncompressed_length, maxsize);
|
|
}
|
|
};
|
|
|
|
TEST_F(ZippedBugReportContentsTest, ContainsMainEntry) {
|
|
ZipEntry main_entry;
|
|
// contains main entry name file
|
|
GetEntry(handle, "main_entry.txt", &main_entry);
|
|
|
|
std::string bugreport_txt_name;
|
|
bugreport_txt_name.resize(main_entry.uncompressed_length);
|
|
ExtractToMemory(handle, &main_entry, reinterpret_cast<uint8_t*>(bugreport_txt_name.data()),
|
|
main_entry.uncompressed_length);
|
|
|
|
// contains main entry file
|
|
FileExists(bugreport_txt_name.c_str(), 1000000U);
|
|
}
|
|
|
|
TEST_F(ZippedBugReportContentsTest, ContainsVersion) {
|
|
ZipEntry entry;
|
|
// contains main entry name file
|
|
GetEntry(handle, "version.txt", &entry);
|
|
|
|
char* buf = new char[entry.uncompressed_length + 1];
|
|
ExtractToMemory(handle, &entry, (uint8_t*)buf, entry.uncompressed_length);
|
|
buf[entry.uncompressed_length] = 0;
|
|
EXPECT_STREQ(buf, ZippedBugreportGenerationTest::ds.version_.c_str());
|
|
delete[] buf;
|
|
}
|
|
|
|
TEST_F(ZippedBugReportContentsTest, ContainsBoardSpecificFiles) {
|
|
// TODO(b/160109027): cf_x86_phone-userdebug does not dump them.
|
|
// FileExists("dumpstate_board.bin", 1000000U, 80000000U);
|
|
// FileExists("dumpstate_board.txt", 100000U, 1000000U);
|
|
}
|
|
|
|
TEST_F(ZippedBugReportContentsTest, ContainsProtoFile) {
|
|
FileExists("proto/activity.proto", 100000U, 1000000U);
|
|
}
|
|
|
|
// Spot check on some files pulled from the file system
|
|
TEST_F(ZippedBugReportContentsTest, ContainsSomeFileSystemFiles) {
|
|
// FS/proc/*/mountinfo size > 0
|
|
FileExists("FS/proc/1/mountinfo", 0U, 100000U);
|
|
|
|
// FS/data/misc/profiles/cur/0/*/primary.prof should exist. Also, since dumpstate only adds
|
|
// profiles to the zip in the non-user build, a build checking is necessary here.
|
|
if (!PropertiesHelper::IsUserBuild()) {
|
|
ZipEntry entry;
|
|
GetEntry(handle, "FS/data/misc/profiles/cur/0/com.android.phone/primary.prof", &entry);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs tests on section data generated by dumpstate and captured by DumpstateListener.
|
|
*/
|
|
class BugreportSectionTest : public Test {
|
|
public:
|
|
ZipArchiveHandle handle;
|
|
|
|
void SetUp() {
|
|
ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath().c_str(), &handle), 0);
|
|
}
|
|
|
|
void TearDown() {
|
|
CloseArchive(handle);
|
|
}
|
|
|
|
static void SetUpTestCase() {
|
|
ParseSections(ZippedBugreportGenerationTest::getZipFilePath().c_str(),
|
|
ZippedBugreportGenerationTest::sections.get());
|
|
}
|
|
|
|
int numMatches(const std::string& substring) {
|
|
int matches = 0;
|
|
for (auto const& section : *ZippedBugreportGenerationTest::sections) {
|
|
if (section.name.find(substring) != std::string::npos) {
|
|
matches++;
|
|
}
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
void SectionExists(const std::string& sectionName, int minsize) {
|
|
for (auto const& section : *ZippedBugreportGenerationTest::sections) {
|
|
if (sectionName == section.name) {
|
|
EXPECT_GE(section.size_bytes, minsize) << " for section:" << sectionName;
|
|
return;
|
|
}
|
|
}
|
|
FAIL() << sectionName << " not found.";
|
|
}
|
|
|
|
/**
|
|
* Whether or not the content of the section is injected by other commands.
|
|
*/
|
|
bool IsContentInjectedByOthers(const std::string& line) {
|
|
// Command header such as `------ APP ACTIVITIES (/system/bin/dumpsys activity -v) ------`.
|
|
static const std::regex kCommandHeader = std::regex{"------ .+ \\(.+\\) ------"};
|
|
std::smatch match;
|
|
if (std::regex_match(line, match, kCommandHeader)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
TEST_F(BugreportSectionTest, Atleast3CriticalDumpsysSectionsGenerated) {
|
|
int numSections = numMatches("CRITICAL");
|
|
EXPECT_GE(numSections, 3);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, Atleast2HighDumpsysSectionsGenerated) {
|
|
int numSections = numMatches("HIGH");
|
|
EXPECT_GE(numSections, 2);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, Atleast50NormalDumpsysSectionsGenerated) {
|
|
int allSections = ZippedBugreportGenerationTest::sections->size();
|
|
int criticalSections = numMatches("CRITICAL");
|
|
int highSections = numMatches("HIGH");
|
|
int normalSections = allSections - criticalSections - highSections;
|
|
|
|
EXPECT_GE(normalSections, 50) << "Total sections less than 50 (Critical:" << criticalSections
|
|
<< "High:" << highSections << "Normal:" << normalSections << ")";
|
|
}
|
|
|
|
// Test if some critical sections are being generated.
|
|
TEST_F(BugreportSectionTest, CriticalSurfaceFlingerSectionGenerated) {
|
|
SectionExists("CRITICAL SurfaceFlinger", /* bytes= */ 10000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, ActivitySectionsGenerated) {
|
|
SectionExists("CRITICAL activity", /* bytes= */ 5000);
|
|
SectionExists("activity", /* bytes= */ 10000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, CpuinfoSectionGenerated) {
|
|
SectionExists("CRITICAL cpuinfo", /* bytes= */ 1000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, WindowSectionGenerated) {
|
|
SectionExists("CRITICAL window", /* bytes= */ 20000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, ConnectivitySectionsGenerated) {
|
|
SectionExists("connectivity", /* bytes= */ 5000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, MeminfoSectionGenerated) {
|
|
SectionExists("HIGH meminfo", /* bytes= */ 100000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, BatteryStatsSectionGenerated) {
|
|
SectionExists("batterystats", /* bytes= */ 1000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, DISABLED_WifiSectionGenerated) {
|
|
SectionExists("wifi", /* bytes= */ 100000);
|
|
}
|
|
|
|
TEST_F(BugreportSectionTest, NoInjectedContentByOtherCommand) {
|
|
// Extract the main entry to a temp file
|
|
TemporaryFile tmp_binary;
|
|
ASSERT_NE(-1, tmp_binary.fd);
|
|
ExtractBugreport(&handle, tmp_binary.fd);
|
|
|
|
// Read line by line and identify sections
|
|
std::ifstream ifs(tmp_binary.path, std::ifstream::in);
|
|
std::string line;
|
|
std::string current_section_name;
|
|
while (std::getline(ifs, line)) {
|
|
std::string section_name;
|
|
if (IsSectionStart(line, §ion_name)) {
|
|
current_section_name = section_name;
|
|
} else if (IsSectionEnd(line)) {
|
|
current_section_name = "";
|
|
} else if (!current_section_name.empty()) {
|
|
EXPECT_FALSE(IsContentInjectedByOthers(line));
|
|
}
|
|
}
|
|
}
|
|
|
|
class DumpstateBinderTest : public Test {
|
|
protected:
|
|
void SetUp() override {
|
|
// In case there is a stray service, stop it first.
|
|
property_set("ctl.stop", "bugreportd");
|
|
// dry_run results in a faster bugreport.
|
|
property_set("dumpstate.dry_run", "true");
|
|
// We need to receive some async calls later. Ensure we have binder threads.
|
|
ProcessState::self()->startThreadPool();
|
|
}
|
|
|
|
void TearDown() override {
|
|
property_set("ctl.stop", "bugreportd");
|
|
property_set("dumpstate.dry_run", "");
|
|
|
|
unlink("/data/local/tmp/tmp.zip");
|
|
unlink("/data/local/tmp/tmp.png");
|
|
}
|
|
|
|
// Waits until listener gets the callbacks.
|
|
void WaitTillExecutionComplete(DumpstateListener* listener) {
|
|
// Wait till one of finished, error or timeout.
|
|
static const int kBugreportTimeoutSeconds = 120;
|
|
int i = 0;
|
|
while (!listener->getIsFinished() && listener->getErrorCode() == -1 &&
|
|
i < kBugreportTimeoutSeconds) {
|
|
sleep(1);
|
|
i++;
|
|
}
|
|
}
|
|
};
|
|
|
|
TEST_F(DumpstateBinderTest, Baseline) {
|
|
// In the beginning dumpstate binder service is not running.
|
|
sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
|
|
EXPECT_EQ(ds_binder, nullptr);
|
|
|
|
// Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
|
|
// and makes it wait.
|
|
property_set("dumpstate.dry_run", "true");
|
|
property_set("ctl.start", "bugreportd");
|
|
|
|
// Now we are able to retrieve dumpstate binder service.
|
|
ds_binder = GetDumpstateService();
|
|
EXPECT_NE(ds_binder, nullptr);
|
|
|
|
// Prepare arguments
|
|
unique_fd bugreport_fd(OpenForWrite("/bugreports/tmp.zip"));
|
|
unique_fd screenshot_fd(OpenForWrite("/bugreports/tmp.png"));
|
|
|
|
EXPECT_NE(bugreport_fd.get(), -1);
|
|
EXPECT_NE(screenshot_fd.get(), -1);
|
|
|
|
sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
|
|
android::binder::Status status =
|
|
ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
|
|
Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener, true);
|
|
// startBugreport is an async call. Verify binder call succeeded first, then wait till listener
|
|
// gets expected callbacks.
|
|
EXPECT_TRUE(status.isOk());
|
|
WaitTillExecutionComplete(listener.get());
|
|
|
|
// Bugreport generation requires user consent, which we cannot get in a test set up,
|
|
// so instead of getting is_finished_, we are more likely to get a consent error.
|
|
EXPECT_TRUE(
|
|
listener->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_DENIED_CONSENT ||
|
|
listener->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
|
|
|
|
// The service should have died on its own, freeing itself up for a new invocation.
|
|
sleep(2);
|
|
ds_binder = GetDumpstateService();
|
|
EXPECT_EQ(ds_binder, nullptr);
|
|
}
|
|
|
|
TEST_F(DumpstateBinderTest, ServiceDies_OnInvalidInput) {
|
|
// Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
|
|
// and makes it wait.
|
|
property_set("ctl.start", "bugreportd");
|
|
sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
|
|
EXPECT_NE(ds_binder, nullptr);
|
|
|
|
// Prepare arguments
|
|
unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
|
|
unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
|
|
|
|
EXPECT_NE(bugreport_fd.get(), -1);
|
|
EXPECT_NE(screenshot_fd.get(), -1);
|
|
|
|
// Call startBugreport with bad arguments.
|
|
sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout))));
|
|
android::binder::Status status =
|
|
ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
|
|
2000, // invalid bugreport mode
|
|
listener, false);
|
|
EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
|
|
|
|
// The service should have died, freeing itself up for a new invocation.
|
|
sleep(2);
|
|
ds_binder = GetDumpstateService();
|
|
EXPECT_EQ(ds_binder, nullptr);
|
|
}
|
|
|
|
TEST_F(DumpstateBinderTest, SimultaneousBugreportsNotAllowed) {
|
|
// Start bugreportd, which runs dumpstate binary with -w; which starts dumpstate service
|
|
// and makes it wait.
|
|
property_set("dumpstate.dry_run", "true");
|
|
property_set("ctl.start", "bugreportd");
|
|
sp<android::os::IDumpstate> ds_binder(GetDumpstateService());
|
|
EXPECT_NE(ds_binder, nullptr);
|
|
|
|
// Prepare arguments
|
|
unique_fd bugreport_fd(OpenForWrite("/data/local/tmp/tmp.zip"));
|
|
unique_fd bugreport_fd2(dup(bugreport_fd.get()));
|
|
unique_fd screenshot_fd(OpenForWrite("/data/local/tmp/tmp.png"));
|
|
unique_fd screenshot_fd2(dup(screenshot_fd.get()));
|
|
|
|
EXPECT_NE(bugreport_fd.get(), -1);
|
|
EXPECT_NE(bugreport_fd2.get(), -1);
|
|
EXPECT_NE(screenshot_fd.get(), -1);
|
|
EXPECT_NE(screenshot_fd2.get(), -1);
|
|
|
|
sp<DumpstateListener> listener1(new DumpstateListener(dup(fileno(stdout))));
|
|
android::binder::Status status =
|
|
ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd), std::move(screenshot_fd),
|
|
Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener1, true);
|
|
EXPECT_TRUE(status.isOk());
|
|
|
|
// try to make another call to startBugreport. This should fail.
|
|
sp<DumpstateListener> listener2(new DumpstateListener(dup(fileno(stdout))));
|
|
status = ds_binder->startBugreport(123, "com.dummy.package", std::move(bugreport_fd2), std::move(screenshot_fd2),
|
|
Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, listener2, true);
|
|
EXPECT_FALSE(status.isOk());
|
|
WaitTillExecutionComplete(listener2.get());
|
|
EXPECT_EQ(listener2->getErrorCode(),
|
|
IDumpstateListener::BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
|
|
|
|
// Meanwhile the first call works as expected. Service should not die in this case.
|
|
WaitTillExecutionComplete(listener1.get());
|
|
|
|
// Bugreport generation requires user consent, which we cannot get in a test set up,
|
|
// so instead of getting is_finished_, we are more likely to get a consent error.
|
|
EXPECT_TRUE(
|
|
listener1->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_DENIED_CONSENT ||
|
|
listener1->getErrorCode() == IDumpstateListener::BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT);
|
|
}
|
|
|
|
} // namespace dumpstate
|
|
} // namespace os
|
|
} // namespace android
|