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 <cutils/properties.h>
25 #include <ziparchive/zip_archive.h>
26 
27 #include "dumpstate.h"
28 
29 #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
30 
31 namespace android {
32 namespace os {
33 namespace dumpstate {
34 
35 using ::testing::Test;
36 using ::std::literals::chrono_literals::operator""s;
37 
38 struct SectionInfo {
39     std::string name;
40     status_t status;
41     int32_t size_bytes;
42     int32_t duration_ms;
43 };
44 
45 /**
46  * Listens to bugreport progress and updates the user by writing the progress to STDOUT. All the
47  * section details generated by dumpstate are added to a vector to be used by Tests later.
48  */
49 class DumpstateListener : public IDumpstateListener {
50   public:
51     int outFd_, max_progress_;
52     std::shared_ptr<std::vector<SectionInfo>> sections_;
DumpstateListener(int fd,std::shared_ptr<std::vector<SectionInfo>> sections)53     DumpstateListener(int fd, std::shared_ptr<std::vector<SectionInfo>> sections)
54         : outFd_(fd), max_progress_(5000), sections_(sections) {
55     }
onProgressUpdated(int32_t progress)56     binder::Status onProgressUpdated(int32_t progress) override {
57         dprintf(outFd_, "\rIn progress %d/%d", progress, max_progress_);
58         return binder::Status::ok();
59     }
onMaxProgressUpdated(int32_t max_progress)60     binder::Status onMaxProgressUpdated(int32_t max_progress) override {
61         max_progress_ = max_progress;
62         return binder::Status::ok();
63     }
onSectionComplete(const::std::string & name,int32_t status,int32_t size_bytes,int32_t duration_ms)64     binder::Status onSectionComplete(const ::std::string& name, int32_t status, int32_t size_bytes,
65                                      int32_t duration_ms) override {
66         sections_->push_back({name, status, size_bytes, duration_ms});
67         return binder::Status::ok();
68     }
onAsBinder()69     IBinder* onAsBinder() override {
70         return nullptr;
71     }
72 };
73 
74 /**
75  * Generates bug report and provide access to the bug report file and other info for other tests.
76  * Since bug report generation is slow, the bugreport is only generated once.
77  */
78 class ZippedBugreportGenerationTest : public Test {
79   public:
80     static std::shared_ptr<std::vector<SectionInfo>> sections;
81     static Dumpstate& ds;
82     static std::chrono::milliseconds duration;
SetUpTestCase()83     static void SetUpTestCase() {
84         property_set("dumpstate.options", "bugreportplus");
85         // clang-format off
86         char* argv[] = {
87             (char*)"dumpstate",
88             (char*)"-d",
89             (char*)"-z",
90             (char*)"-B",
91             (char*)"-o",
92             (char*)dirname(android::base::GetExecutablePath().c_str())
93         };
94         // clang-format on
95         sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout)), sections));
96         ds.listener_ = listener;
97         ds.listener_name_ = "Smokey";
98         ds.report_section_ = true;
99         auto start = std::chrono::steady_clock::now();
100         run_main(ARRAY_SIZE(argv), argv);
101         auto end = std::chrono::steady_clock::now();
102         duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
103     }
104 
getZipFilePath()105     static const char* getZipFilePath() {
106         return ds.GetPath(".zip").c_str();
107     }
108 };
109 std::shared_ptr<std::vector<SectionInfo>> ZippedBugreportGenerationTest::sections =
110     std::make_shared<std::vector<SectionInfo>>();
111 Dumpstate& ZippedBugreportGenerationTest::ds = Dumpstate::GetInstance();
112 std::chrono::milliseconds ZippedBugreportGenerationTest::duration = 0s;
113 
TEST_F(ZippedBugreportGenerationTest,IsGeneratedWithoutErrors)114 TEST_F(ZippedBugreportGenerationTest, IsGeneratedWithoutErrors) {
115     EXPECT_EQ(access(getZipFilePath(), F_OK), 0);
116 }
117 
TEST_F(ZippedBugreportGenerationTest,Is3MBto30MBinSize)118 TEST_F(ZippedBugreportGenerationTest, Is3MBto30MBinSize) {
119     struct stat st;
120     EXPECT_EQ(stat(getZipFilePath(), &st), 0);
121     EXPECT_GE(st.st_size, 3000000 /* 3MB */);
122     EXPECT_LE(st.st_size, 30000000 /* 30MB */);
123 }
124 
TEST_F(ZippedBugreportGenerationTest,TakesBetween30And150Seconds)125 TEST_F(ZippedBugreportGenerationTest, TakesBetween30And150Seconds) {
126     EXPECT_GE(duration, 30s) << "Expected completion in more than 30s. Actual time "
127                              << duration.count() << " s.";
128     EXPECT_LE(duration, 150s) << "Expected completion in less than 150s. Actual time "
129                               << duration.count() << " s.";
130 }
131 
132 /**
133  * Run tests on contents of zipped bug report.
134  */
135 class ZippedBugReportContentsTest : public Test {
136   public:
137     ZipArchiveHandle handle;
SetUp()138     void SetUp() {
139         ASSERT_EQ(OpenArchive(ZippedBugreportGenerationTest::getZipFilePath(), &handle), 0);
140     }
TearDown()141     void TearDown() {
142         CloseArchive(handle);
143     }
144 
FileExists(const char * filename,uint32_t minsize,uint32_t maxsize)145     void FileExists(const char* filename, uint32_t minsize, uint32_t maxsize) {
146         ZipEntry entry;
147         EXPECT_EQ(FindEntry(handle, ZipString(filename), &entry), 0);
148         EXPECT_GT(entry.uncompressed_length, minsize);
149         EXPECT_LT(entry.uncompressed_length, maxsize);
150     }
151 };
152 
TEST_F(ZippedBugReportContentsTest,ContainsMainEntry)153 TEST_F(ZippedBugReportContentsTest, ContainsMainEntry) {
154     ZipEntry mainEntryLoc;
155     // contains main entry name file
156     EXPECT_EQ(FindEntry(handle, ZipString("main_entry.txt"), &mainEntryLoc), 0);
157 
158     char* buf = new char[mainEntryLoc.uncompressed_length];
159     ExtractToMemory(handle, &mainEntryLoc, (uint8_t*)buf, mainEntryLoc.uncompressed_length);
160     delete[] buf;
161 
162     // contains main entry file
163     FileExists(buf, 1000000U, 50000000U);
164 }
165 
TEST_F(ZippedBugReportContentsTest,ContainsVersion)166 TEST_F(ZippedBugReportContentsTest, ContainsVersion) {
167     ZipEntry entry;
168     // contains main entry name file
169     EXPECT_EQ(FindEntry(handle, ZipString("version.txt"), &entry), 0);
170 
171     char* buf = new char[entry.uncompressed_length + 1];
172     ExtractToMemory(handle, &entry, (uint8_t*)buf, entry.uncompressed_length);
173     buf[entry.uncompressed_length] = 0;
174     EXPECT_STREQ(buf, ZippedBugreportGenerationTest::ds.version_.c_str());
175     delete[] buf;
176 }
177 
TEST_F(ZippedBugReportContentsTest,ContainsBoardSpecificFiles)178 TEST_F(ZippedBugReportContentsTest, ContainsBoardSpecificFiles) {
179     FileExists("dumpstate_board.bin", 1000000U, 80000000U);
180     FileExists("dumpstate_board.txt", 100000U, 1000000U);
181 }
182 
183 // Spot check on some files pulled from the file system
TEST_F(ZippedBugReportContentsTest,ContainsSomeFileSystemFiles)184 TEST_F(ZippedBugReportContentsTest, ContainsSomeFileSystemFiles) {
185     // FS/proc/*/mountinfo size > 0
186     FileExists("FS/proc/1/mountinfo", 0U, 100000U);
187 
188     // FS/data/misc/profiles/cur/0/*/primary.prof size > 0
189     FileExists("FS/data/misc/profiles/cur/0/com.android.phone/primary.prof", 0U, 100000U);
190 }
191 
192 /**
193  * Runs tests on section data generated by dumpstate and captured by DumpstateListener.
194  */
195 class BugreportSectionTest : public Test {
196   public:
numMatches(const std::string & substring)197     int numMatches(const std::string& substring) {
198         int matches = 0;
199         for (auto const& section : *ZippedBugreportGenerationTest::sections) {
200             if (section.name.find(substring) != std::string::npos) {
201                 matches++;
202             }
203         }
204         return matches;
205     }
SectionExists(const std::string & sectionName,int minsize)206     void SectionExists(const std::string& sectionName, int minsize) {
207         for (auto const& section : *ZippedBugreportGenerationTest::sections) {
208             if (sectionName == section.name) {
209                 EXPECT_GE(section.size_bytes, minsize);
210                 return;
211             }
212         }
213         FAIL() << sectionName << " not found.";
214     }
215 };
216 
217 // Test all sections are generated without timeouts or errors
TEST_F(BugreportSectionTest,GeneratedWithoutErrors)218 TEST_F(BugreportSectionTest, GeneratedWithoutErrors) {
219     for (auto const& section : *ZippedBugreportGenerationTest::sections) {
220         EXPECT_EQ(section.status, 0) << section.name << " failed with status " << section.status;
221     }
222 }
223 
TEST_F(BugreportSectionTest,Atleast3CriticalDumpsysSectionsGenerated)224 TEST_F(BugreportSectionTest, Atleast3CriticalDumpsysSectionsGenerated) {
225     int numSections = numMatches("DUMPSYS CRITICAL");
226     EXPECT_GE(numSections, 3);
227 }
228 
TEST_F(BugreportSectionTest,Atleast2HighDumpsysSectionsGenerated)229 TEST_F(BugreportSectionTest, Atleast2HighDumpsysSectionsGenerated) {
230     int numSections = numMatches("DUMPSYS HIGH");
231     EXPECT_GE(numSections, 2);
232 }
233 
TEST_F(BugreportSectionTest,Atleast50NormalDumpsysSectionsGenerated)234 TEST_F(BugreportSectionTest, Atleast50NormalDumpsysSectionsGenerated) {
235     int allSections = numMatches("DUMPSYS");
236     int criticalSections = numMatches("DUMPSYS CRITICAL");
237     int highSections = numMatches("DUMPSYS HIGH");
238     int normalSections = allSections - criticalSections - highSections;
239 
240     EXPECT_GE(normalSections, 50) << "Total sections less than 50 (Critical:" << criticalSections
241                                   << "High:" << highSections << "Normal:" << normalSections << ")";
242 }
243 
TEST_F(BugreportSectionTest,Atleast1ProtoDumpsysSectionGenerated)244 TEST_F(BugreportSectionTest, Atleast1ProtoDumpsysSectionGenerated) {
245     int numSections = numMatches("proto/");
246     EXPECT_GE(numSections, 1);
247 }
248 
249 // Test if some critical sections are being generated.
TEST_F(BugreportSectionTest,CriticalSurfaceFlingerSectionGenerated)250 TEST_F(BugreportSectionTest, CriticalSurfaceFlingerSectionGenerated) {
251     SectionExists("DUMPSYS CRITICAL - SurfaceFlinger", /* bytes= */ 10000);
252 }
253 
TEST_F(BugreportSectionTest,ActivitySectionsGenerated)254 TEST_F(BugreportSectionTest, ActivitySectionsGenerated) {
255     SectionExists("DUMPSYS CRITICAL - activity", /* bytes= */ 5000);
256     SectionExists("DUMPSYS - activity", /* bytes= */ 10000);
257 }
258 
TEST_F(BugreportSectionTest,CpuinfoSectionGenerated)259 TEST_F(BugreportSectionTest, CpuinfoSectionGenerated) {
260     SectionExists("DUMPSYS CRITICAL - cpuinfo", /* bytes= */ 1000);
261 }
262 
TEST_F(BugreportSectionTest,WindowSectionGenerated)263 TEST_F(BugreportSectionTest, WindowSectionGenerated) {
264     SectionExists("DUMPSYS CRITICAL - window", /* bytes= */ 20000);
265 }
266 
TEST_F(BugreportSectionTest,ConnectivitySectionsGenerated)267 TEST_F(BugreportSectionTest, ConnectivitySectionsGenerated) {
268     SectionExists("DUMPSYS HIGH - connectivity", /* bytes= */ 5000);
269     SectionExists("DUMPSYS - connectivity", /* bytes= */ 5000);
270 }
271 
TEST_F(BugreportSectionTest,MeminfoSectionGenerated)272 TEST_F(BugreportSectionTest, MeminfoSectionGenerated) {
273     SectionExists("DUMPSYS HIGH - meminfo", /* bytes= */ 100000);
274 }
275 
TEST_F(BugreportSectionTest,BatteryStatsSectionGenerated)276 TEST_F(BugreportSectionTest, BatteryStatsSectionGenerated) {
277     SectionExists("DUMPSYS - batterystats", /* bytes= */ 1000);
278 }
279 
TEST_F(BugreportSectionTest,WifiSectionGenerated)280 TEST_F(BugreportSectionTest, WifiSectionGenerated) {
281     SectionExists("DUMPSYS - wifi", /* bytes= */ 100000);
282 }
283 
284 }  // namespace dumpstate
285 }  // namespace os
286 }  // namespace android
287