/* * 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 <gmock/gmock.h> #include <gtest/gtest.h> #include <fcntl.h> #include <libgen.h> #include <android-base/file.h> #include <cutils/properties.h> #include <ziparchive/zip_archive.h> #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; struct SectionInfo { std::string name; status_t status; int32_t size_bytes; int32_t duration_ms; }; /** * 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 IDumpstateListener { public: int outFd_, max_progress_; std::shared_ptr<std::vector<SectionInfo>> sections_; DumpstateListener(int fd, std::shared_ptr<std::vector<SectionInfo>> sections) : outFd_(fd), max_progress_(5000), sections_(sections) { } binder::Status onProgressUpdated(int32_t progress) override { dprintf(outFd_, "\rIn progress %d/%d", progress, max_progress_); return binder::Status::ok(); } binder::Status onMaxProgressUpdated(int32_t max_progress) override { max_progress_ = max_progress; return binder::Status::ok(); } binder::Status onSectionComplete(const ::std::string& name, int32_t status, int32_t size_bytes, int32_t duration_ms) override { sections_->push_back({name, status, size_bytes, duration_ms}); return binder::Status::ok(); } IBinder* onAsBinder() override { return nullptr; } }; /** * 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 SetUpTestCase() { property_set("dumpstate.options", "bugreportplus"); // clang-format off char* argv[] = { (char*)"dumpstate", (char*)"-d", (char*)"-z", (char*)"-B", (char*)"-o", (char*)dirname(android::base::GetExecutablePath().c_str()) }; // clang-format on sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout)), sections)); ds.listener_ = listener; ds.listener_name_ = "Smokey"; ds.report_section_ = true; auto start = std::chrono::steady_clock::now(); run_main(ARRAY_SIZE(argv), argv); auto end = std::chrono::steady_clock::now(); duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); } static const char* getZipFilePath() { return ds.GetPath(".zip").c_str(); } }; 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) { EXPECT_EQ(access(getZipFilePath(), F_OK), 0); } TEST_F(ZippedBugreportGenerationTest, Is3MBto30MBinSize) { struct stat st; EXPECT_EQ(stat(getZipFilePath(), &st), 0); EXPECT_GE(st.st_size, 3000000 /* 3MB */); EXPECT_LE(st.st_size, 30000000 /* 30MB */); } TEST_F(ZippedBugreportGenerationTest, TakesBetween30And150Seconds) { EXPECT_GE(duration, 30s) << "Expected completion in more than 30s. Actual time " << duration.count() << " s."; EXPECT_LE(duration, 150s) << "Expected completion in less than 150s. Actual time " << duration.count() << " s."; } /** * Run tests on contents of zipped bug report. */ class ZippedBugReportContentsTest : public Test { public: ZipArchiveHandle handle; void SetUp() { ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath(), &handle), 0); } void TearDown() { CloseArchive(handle); } void FileExists(const char* filename, uint32_t minsize, uint32_t maxsize) { ZipEntry entry; EXPECT_EQ(FindEntry(handle, ZipString(filename), &entry), 0); EXPECT_GT(entry.uncompressed_length, minsize); EXPECT_LT(entry.uncompressed_length, maxsize); } }; TEST_F(ZippedBugReportContentsTest, ContainsMainEntry) { ZipEntry mainEntryLoc; // contains main entry name file EXPECT_EQ(FindEntry(handle, ZipString("main_entry.txt"), &mainEntryLoc), 0); char* buf = new char[mainEntryLoc.uncompressed_length]; ExtractToMemory(handle, &mainEntryLoc, (uint8_t*)buf, mainEntryLoc.uncompressed_length); delete[] buf; // contains main entry file FileExists(buf, 1000000U, 50000000U); } TEST_F(ZippedBugReportContentsTest, ContainsVersion) { ZipEntry entry; // contains main entry name file EXPECT_EQ(FindEntry(handle, ZipString("version.txt"), &entry), 0); 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) { FileExists("dumpstate_board.bin", 1000000U, 80000000U); FileExists("dumpstate_board.txt", 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 size > 0 FileExists("FS/data/misc/profiles/cur/0/com.android.phone/primary.prof", 0U, 100000U); } /** * Runs tests on section data generated by dumpstate and captured by DumpstateListener. */ class BugreportSectionTest : public Test { public: 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); return; } } FAIL() << sectionName << " not found."; } }; // Test all sections are generated without timeouts or errors TEST_F(BugreportSectionTest, GeneratedWithoutErrors) { for (auto const& section : *ZippedBugreportGenerationTest::sections) { EXPECT_EQ(section.status, 0) << section.name << " failed with status " << section.status; } } TEST_F(BugreportSectionTest, Atleast3CriticalDumpsysSectionsGenerated) { int numSections = numMatches("DUMPSYS CRITICAL"); EXPECT_GE(numSections, 3); } TEST_F(BugreportSectionTest, Atleast2HighDumpsysSectionsGenerated) { int numSections = numMatches("DUMPSYS HIGH"); EXPECT_GE(numSections, 2); } TEST_F(BugreportSectionTest, Atleast50NormalDumpsysSectionsGenerated) { int allSections = numMatches("DUMPSYS"); int criticalSections = numMatches("DUMPSYS CRITICAL"); int highSections = numMatches("DUMPSYS HIGH"); int normalSections = allSections - criticalSections - highSections; EXPECT_GE(normalSections, 50) << "Total sections less than 50 (Critical:" << criticalSections << "High:" << highSections << "Normal:" << normalSections << ")"; } TEST_F(BugreportSectionTest, Atleast1ProtoDumpsysSectionGenerated) { int numSections = numMatches("proto/"); EXPECT_GE(numSections, 1); } // Test if some critical sections are being generated. TEST_F(BugreportSectionTest, CriticalSurfaceFlingerSectionGenerated) { SectionExists("DUMPSYS CRITICAL - SurfaceFlinger", /* bytes= */ 10000); } TEST_F(BugreportSectionTest, ActivitySectionsGenerated) { SectionExists("DUMPSYS CRITICAL - activity", /* bytes= */ 5000); SectionExists("DUMPSYS - activity", /* bytes= */ 10000); } TEST_F(BugreportSectionTest, CpuinfoSectionGenerated) { SectionExists("DUMPSYS CRITICAL - cpuinfo", /* bytes= */ 1000); } TEST_F(BugreportSectionTest, WindowSectionGenerated) { SectionExists("DUMPSYS CRITICAL - window", /* bytes= */ 20000); } TEST_F(BugreportSectionTest, ConnectivitySectionsGenerated) { SectionExists("DUMPSYS HIGH - connectivity", /* bytes= */ 5000); SectionExists("DUMPSYS - connectivity", /* bytes= */ 5000); } TEST_F(BugreportSectionTest, MeminfoSectionGenerated) { SectionExists("DUMPSYS HIGH - meminfo", /* bytes= */ 100000); } TEST_F(BugreportSectionTest, BatteryStatsSectionGenerated) { SectionExists("DUMPSYS - batterystats", /* bytes= */ 1000); } TEST_F(BugreportSectionTest, WifiSectionGenerated) { SectionExists("DUMPSYS - wifi", /* bytes= */ 100000); } } // namespace dumpstate } // namespace os } // namespace android