Add tests for reporter class
Test: Add test for Reporter class
Change-Id: Ic1d87a26dd4b8271bab7b03374c7a1d4d7b87f92
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index a8a5483..537c910 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -79,7 +79,10 @@
src/Reporter.cpp \
src/Section.cpp \
src/protobuf.cpp \
+ src/report_directory.cpp \
+ src/section_list.cpp \
tests/FdBuffer_test.cpp \
+ tests/Reporter_test.cpp \
tests/Section_test.cpp \
LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 7c6789e..c4b54bb 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -153,7 +153,6 @@
break;
}
reporter->batch.add(request);
- reporter->args.merge(request->args);
}
// Take the report, which might take a while. More requests might queue
@@ -235,7 +234,7 @@
return Status::fromExceptionCode(Status::EX_SECURITY,
"Only system uid can call systemRunning");
}
-
+
// When system_server is up and running, schedule the dropbox task to run.
mHandler->scheduleSendBacklogToDropbox();
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 2a4f89e..ea73bcd 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -35,7 +35,7 @@
/**
* The directory where the incident reports are stored.
*/
-static const String8 INCIDENT_DIRECTORY("/data/misc/incidents");
+static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/";
// ================================================================================
static status_t write_all(int fd, uint8_t const* buf, size_t size)
@@ -68,6 +68,7 @@
// ================================================================================
ReportRequestSet::ReportRequestSet()
:mRequests(),
+ mSections(),
mWritableCount(0),
mMainFd(-1)
{
@@ -77,10 +78,12 @@
{
}
+// TODO: dedup on exact same args and fd, report the status back to listener!
void
ReportRequestSet::add(const sp<ReportRequest>& request)
{
mRequests.push_back(request);
+ mSections.merge(request->args);
mWritableCount++;
}
@@ -122,11 +125,16 @@
return mWritableCount > 0 ? NO_ERROR : err;
}
+bool
+ReportRequestSet::containsSection(int id) {
+ return mSections.containsSection(id);
+}
// ================================================================================
-Reporter::Reporter()
- :args(),
- batch()
+Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; };
+
+Reporter::Reporter(const char* directory)
+ :batch()
{
char buf[100];
@@ -134,10 +142,15 @@
mMaxSize = 100 * 1024 * 1024;
mMaxCount = 100;
+ // string ends up with '/' is a directory
+ String8 dir = String8(directory);
+ if (directory[dir.size() - 1] != '/') dir += "/";
+ mIncidentDirectory = dir.string();
+
// There can't be two at the same time because it's on one thread.
mStartTime = time(NULL);
- strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
- mFilename = INCIDENT_DIRECTORY + buf;
+ strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
+ mFilename = mIncidentDirectory + buf;
}
Reporter::~Reporter()
@@ -161,7 +174,7 @@
}
if (needMainFd) {
// Create the directory
- err = create_directory(INCIDENT_DIRECTORY);
+ if (!isTest) err = create_directory(mIncidentDirectory);
if (err != NO_ERROR) {
goto done;
}
@@ -169,7 +182,7 @@
// If there are too many files in the directory (for whatever reason),
// delete the oldest ones until it's under the limit. Doing this first
// does mean that we can go over, so the max size is not a hard limit.
- clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount);
+ if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount);
// Open the file.
err = create_file(&mainFd);
@@ -214,7 +227,7 @@
const int id = (*section)->id;
ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string());
- if (this->args.containsSection(id)) {
+ if (this->batch.containsSection(id)) {
// Notify listener of starting
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
@@ -270,7 +283,7 @@
// If the status was ok, delete the file. If not, leave it around until the next
// boot or the next checkin. If the directory gets too big older files will
// be rotated out.
- unlink(mFilename.c_str());
+ if(!isTest) unlink(mFilename.c_str());
}
return REPORT_FINISHED;
@@ -284,7 +297,7 @@
{
const char* filename = mFilename.c_str();
- *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660);
+ *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660);
if (*fd < 0) {
ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno));
return -errno;
@@ -303,20 +316,24 @@
return NO_ERROR;
}
-// ================================================================================
Reporter::run_report_status_t
Reporter::upload_backlog()
{
DIR* dir;
struct dirent* entry;
struct stat st;
+ status_t err;
- if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) {
- ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string());
+ if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) {
+ ALOGE("directory doesn't exist: %s", strerror(-err));
+ return REPORT_FINISHED;
+ }
+
+ if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) {
+ ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY);
return REPORT_NEEDS_DROPBOX;
}
- String8 dirbase(INCIDENT_DIRECTORY + "/");
sp<DropBoxManager> dropbox = new DropBoxManager();
// Enumerate, count and add up size
@@ -324,7 +341,7 @@
if (entry->d_name[0] == '.') {
continue;
}
- String8 filename = dirbase + entry->d_name;
+ String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name;
if (stat(filename.string(), &st) != 0) {
ALOGE("Unable to stat file %s", filename.string());
continue;
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index 5b86561..509611c 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -62,8 +62,10 @@
iterator begin() { return mRequests.begin(); }
iterator end() { return mRequests.end(); }
+ bool containsSection(int id);
private:
vector<sp<ReportRequest>> mRequests;
+ IncidentReportArgs mSections;
int mWritableCount;
int mMainFd;
};
@@ -77,10 +79,10 @@
REPORT_NEEDS_DROPBOX = 1
};
- IncidentReportArgs args;
ReportRequestSet batch;
Reporter();
+ Reporter(const char* directory);
virtual ~Reporter();
// Run the report as described in the batch and args parameters.
@@ -89,12 +91,16 @@
static run_report_status_t upload_backlog();
private:
+ String8 mIncidentDirectory;
+
string mFilename;
off_t mMaxSize;
size_t mMaxCount;
time_t mStartTime;
status_t create_file(int* fd);
+
+ bool isTest = true; // default to true for testing
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 8ef6817..e3f2b36 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -424,7 +424,7 @@
status_t cmdStatus = waitForChild(cmdPid);
status_t ihStatus = waitForChild(ihPid);
if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
- ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incidnet helper: %s",
+ ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incident helper: %s",
this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
return cmdStatus != NO_ERROR ? cmdStatus : ihStatus;
}
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
index f60b8ac..110902c 100644
--- a/cmds/incidentd/src/report_directory.cpp
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -129,7 +129,8 @@
return;
}
- String8 dirbase(String8(directory) + "/");
+ String8 dirbase(directory);
+ if (directory[dirbase.size() - 1] != '/') dirbase += "/";
off_t totalSize = 0;
size_t totalCount = 0;
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
new file mode 100644
index 0000000..a774741
--- /dev/null
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -0,0 +1,197 @@
+// Copyright (C) 2017 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.
+
+#define LOG_TAG "incidentd"
+
+#include "Reporter.h"
+
+#include <android/os/BnIncidentReportStatusListener.h>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <dirent.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string.h>
+
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStdout;
+
+class TestListener : public IIncidentReportStatusListener
+{
+public:
+ int startInvoked;
+ int finishInvoked;
+ int failedInvoked;
+ map<int, int> startSections;
+ map<int, int> finishSections;
+
+ TestListener() : startInvoked(0), finishInvoked(0), failedInvoked(0) {};
+ virtual ~TestListener() {};
+
+ virtual Status onReportStarted() {
+ startInvoked++;
+ return Status::ok();
+ };
+ virtual Status onReportSectionStatus(int section, int status) {
+ switch (status) {
+ case IIncidentReportStatusListener::STATUS_STARTING:
+ if (startSections.count(section) == 0)
+ startSections[section] = 0;
+ startSections[section] = startSections[section] + 1;
+ break;
+ case IIncidentReportStatusListener::STATUS_FINISHED:
+ if (finishSections.count(section) == 0)
+ finishSections[section] = 0;
+ finishSections[section] = finishSections[section] + 1;
+ break;
+ }
+ return Status::ok();
+ };
+ virtual Status onReportFinished() {
+ finishInvoked++;
+ return Status::ok();
+ };
+ virtual Status onReportFailed() {
+ failedInvoked++;
+ return Status::ok();
+ };
+
+protected:
+ IBinder* onAsBinder() override { return nullptr; };
+
+};
+
+class ReporterTest : public Test {
+public:
+ virtual void SetUp() {
+ reporter = new Reporter(td.path);
+ l = new TestListener();
+ }
+
+ vector<string> InspectFiles() {
+ DIR* dir;
+ struct dirent* entry;
+ vector<string> results;
+
+ string dirbase = string(td.path) + "/";
+ dir = opendir(td.path);
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ string filename = dirbase + entry->d_name;
+ string content;
+ ReadFileToString(filename, &content);
+ results.push_back(content);
+ }
+ return results;
+ }
+
+protected:
+ TemporaryDir td;
+ ReportRequestSet requests;
+ sp<Reporter> reporter;
+ sp<TestListener> l;
+};
+
+TEST_F(ReporterTest, IncidentReportArgs) {
+ IncidentReportArgs args1, args2;
+ args1.addSection(1);
+ args2.addSection(3);
+
+ args1.merge(args2);
+ ASSERT_TRUE(args1.containsSection(1));
+ ASSERT_FALSE(args1.containsSection(2));
+ ASSERT_TRUE(args1.containsSection(3));
+}
+
+TEST_F(ReporterTest, ReportRequestSetEmpty) {
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ requests.write((uint8_t *) "abcdef", 6);
+ EXPECT_THAT(GetCapturedStdout(), StrEq("abcdef"));
+}
+
+TEST_F(ReporterTest, WriteToStreamFdAndMainFd) {
+ TemporaryFile tf;
+ IncidentReportArgs args;
+ sp<ReportRequest> r = new ReportRequest(args, l, tf.fd);
+
+ requests.add(r);
+ requests.setMainFd(STDOUT_FILENO);
+
+ const char* data = "abcdef";
+
+ CaptureStdout();
+ requests.write((uint8_t *) data, 6);
+ EXPECT_THAT(GetCapturedStdout(), StrEq(data));
+
+ string content;
+ ASSERT_TRUE(ReadFileToString(tf.path, &content));
+ EXPECT_THAT(content, StrEq(data));
+}
+
+TEST_F(ReporterTest, RunReportEmpty) {
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ EXPECT_EQ(l->startInvoked, 0);
+ EXPECT_EQ(l->finishInvoked, 0);
+ EXPECT_TRUE(l->startSections.empty());
+ EXPECT_TRUE(l->finishSections.empty());
+ EXPECT_EQ(l->failedInvoked, 0);
+}
+
+TEST_F(ReporterTest, RunReportWithHeaders) {
+ IncidentReportArgs args1, args2;
+ args1.addSection(1);
+ args2.addSection(2);
+ std::vector<int8_t> header {'a', 'b', 'c', 'd', 'e'};
+ args2.addHeader(header);
+ sp<ReportRequest> r1 = new ReportRequest(args1, l, STDOUT_FILENO);
+ sp<ReportRequest> r2 = new ReportRequest(args2, l, STDOUT_FILENO);
+
+ reporter->batch.add(r1);
+ reporter->batch.add(r2);
+
+ CaptureStdout();
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "abcde"));
+ EXPECT_EQ(l->startInvoked, 2);
+ EXPECT_EQ(l->finishInvoked, 2);
+ EXPECT_TRUE(l->startSections.empty());
+ EXPECT_TRUE(l->finishSections.empty());
+ EXPECT_EQ(l->failedInvoked, 0);
+}
+
+TEST_F(ReporterTest, RunReportToGivenDirectory) {
+ IncidentReportArgs args;
+ args.addHeader({'1', '2', '3'});
+ args.addHeader({'a', 'b', 'c', 'd'});
+ sp<ReportRequest> r = new ReportRequest(args, l, -1);
+ reporter->batch.add(r);
+
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ vector<string> results = InspectFiles();
+ ASSERT_EQ((int)results.size(), 1);
+ EXPECT_EQ(results[0], "\n\x3" "123\n\x4" "abcd");
+}