Added initial tests for dumpstate.
BUG: 31807540
Test: mmm -j32 frameworks/native/cmds/dumpstate/ && adb push ${ANDROID_PRODUCT_OUT}/data/nativetest/dumpstate_test* /data/nativetest && adb shell /data/nativetest/dumpstate_test/dumpstate_test
Change-Id: If5497784052b8d13d7c856f9400dbcd8c2015d05
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
index 44a994c..2eb512f 100644
--- a/cmds/dumpstate/Android.mk
+++ b/cmds/dumpstate/Android.mk
@@ -1,30 +1,84 @@
LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
+# ================#
+# Common settings #
+# ================#
+# ZipArchive support, the order matters here to get all symbols.
+COMMON_ZIP_LIBRARIES := libziparchive libz libcrypto_static
-ifdef BOARD_WLAN_DEVICE
-LOCAL_CFLAGS := -DFWDUMP_$(BOARD_WLAN_DEVICE)
-endif
-
-LOCAL_SRC_FILES := \
- dumpstate.cpp \
+# TODO: ideally the tests should depend on a shared dumpstate library, but currently libdumpstate
+# is used to define the device-specific HAL library. Instead, both dumpstate and dumpstate_test
+# shares a lot of common settings
+COMMON_LOCAL_CFLAGS := \
+ -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
+COMMON_SRC_FILES := \
utils.cpp
-
-LOCAL_MODULE := dumpstate
-
-LOCAL_SHARED_LIBRARIES := \
+COMMON_SHARED_LIBRARIES := \
libbase \
libcutils \
libhardware_legacy \
liblog \
libselinux
-# ZipArchive support, the order matters here to get all symbols.
-ZIP_LIBRARIES := libziparchive libz libcrypto_static
+# ==========#
+# dumpstate #
+# ==========#
+include $(CLEAR_VARS)
-LOCAL_STATIC_LIBRARIES := $(ZIP_LIBRARIES)
+ifdef BOARD_WLAN_DEVICE
+LOCAL_CFLAGS := -DFWDUMP_$(BOARD_WLAN_DEVICE)
+endif
+
+LOCAL_SRC_FILES := $(COMMON_SRC_FILES) \
+ dumpstate.cpp
+
+LOCAL_MODULE := dumpstate
+
+LOCAL_SHARED_LIBRARIES := $(COMMON_SHARED_LIBRARIES)
+
+LOCAL_STATIC_LIBRARIES := $(COMMON_ZIP_LIBRARIES)
+
LOCAL_HAL_STATIC_LIBRARIES := libdumpstate
-LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter
+
+LOCAL_CFLAGS += $(COMMON_LOCAL_CFLAGS)
+
LOCAL_INIT_RC := dumpstate.rc
include $(BUILD_EXECUTABLE)
+
+# ===============#
+# dumpstate_test #
+# ===============#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := dumpstate_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
+
+LOCAL_SRC_FILES := $(COMMON_SRC_FILES) \
+ tests/dumpstate_test.cpp
+
+LOCAL_STATIC_LIBRARIES := $(COMMON_ZIP_LIBRARIES) \
+ libgmock
+
+LOCAL_SHARED_LIBRARIES := $(COMMON_SHARED_LIBRARIES)
+
+include $(BUILD_NATIVE_TEST)
+
+# =======================#
+# dumpstate_test_fixture #
+# =======================#
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := dumpstate_test_fixture
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
+
+LOCAL_SRC_FILES := \
+ tests/dumpstate_test_fixture.cpp
+
+include $(BUILD_NATIVE_TEST)
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index f3e68b3..637b784 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -62,7 +62,6 @@
static std::string args;
// TODO: variables below should be part of dumpstate object
-static std::string buildType;
static time_t now;
static std::unique_ptr<ZipWriter> zip_writer;
static std::set<std::string> mount_points;
@@ -127,14 +126,6 @@
static constexpr char PROPERTY_EXTRA_OPTIONS[] = "dumpstate.options";
static constexpr char PROPERTY_LAST_ID[] = "dumpstate.last_id";
-bool Dumpstate::IsUserBuild() {
- return "user" == buildType;
-}
-
-bool Dumpstate::IsDryRun() {
- return dryRun_;
-}
-
/* gets the tombstone data, according to the bugreport type: if zipped, gets all tombstones;
* otherwise, gets just those modified in the last half an hour. */
static void get_tombstone_fds(tombstone_data_t data[NUM_TOMBSTONES]) {
@@ -155,7 +146,7 @@
}
// for_each_pid() callback to get mount info about a process.
-void do_mountinfo(int pid, const char *name) {
+void do_mountinfo(int pid, const char* name __attribute__((unused))) {
char path[PATH_MAX];
// Gets the the content of the /proc/PID/ns/mnt link, so only unique mount points
@@ -426,7 +417,7 @@
return strcmp(path + len - sizeof(stat) + 1, stat); /* .../stat? */
}
-static bool skip_none(const char *path) {
+static bool skip_none(const char* path __attribute__((unused))) {
return false;
}
@@ -704,7 +695,7 @@
build = android::base::GetProperty("ro.build.display.id", "(unknown)");
fingerprint = android::base::GetProperty("ro.build.fingerprint", "(unknown)");
- buildType = android::base::GetProperty("ro.build.type", "(unknown)");
+ ds.buildType_ = android::base::GetProperty("ro.build.type", "(unknown)");
radio = android::base::GetProperty("gsm.version.baseband", "(unknown)");
bootloader = android::base::GetProperty("ro.bootloader", "(unknown)");
network = android::base::GetProperty("gsm.operator.alpha", "(unknown)");
@@ -727,7 +718,7 @@
printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
printf("Bugreport format version: %s\n", version.c_str());
printf("Dumpstate info: id=%lu pid=%d dryRun=%d args=%s extraOptions=%s\n", ds.id_, getpid(),
- ds.dryRun_, args.c_str(), extraOptions.c_str());
+ ds.IsDryRun(), args.c_str(), extraOptions.c_str());
printf("\n");
}
@@ -805,7 +796,7 @@
}
/* adds a file to the existing zipped bugreport */
-static int _add_file_from_fd(const char *title, const char *path, int fd) {
+static int _add_file_from_fd(const char* title __attribute__((unused)), const char* path, int fd) {
return add_zip_entry_from_fd(ZIP_ROOT_DIR + path, fd) ? 0 : 1;
}
@@ -861,7 +852,8 @@
RunCommand("IP6TABLE RAW", {"ip6tables", "-t", "raw", "-L", "-nvx"});
}
-static void dumpstate(const std::string& screenshot_path, const std::string& version) {
+static void dumpstate(const std::string& screenshot_path,
+ const std::string& version __attribute__((unused))) {
DurationReporter durationReporter("DUMPSTATE");
unsigned long timeout;
@@ -1193,7 +1185,7 @@
}
}
-static void sig_handler(int signo) {
+static void sig_handler(int signo __attribute__((unused))) {
wake_lock_releaser();
_exit(EXIT_FAILURE);
}
@@ -1319,8 +1311,7 @@
register_sig_handler();
}
- ds.dryRun_ = android::base::GetBoolProperty("dumpstate.dry_run", false);
- if (ds.dryRun_) {
+ if (ds.IsDryRun()) {
MYLOGI("Running on dry-run mode (to disable it, call 'setprop dumpstate.dry_run false')\n");
}
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 70af270..1c570b0 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -202,6 +202,8 @@
* that are spread accross utils.cpp and dumpstate.cpp will be moved to it.
*/
class Dumpstate {
+ friend class DumpstateTest;
+
public:
static Dumpstate& GetInstance();
@@ -274,15 +276,18 @@
// When set, defines a socket file-descriptor use to report progress to bugreportz.
int controlSocketFd_ = -1;
- // Whether this is a dry run.
- bool dryRun_;
+ // Build type (such as 'user' or 'eng').
+ std::string buildType_;
// Full path of the directory where the bugreport files will be written;
std::string bugreportDir_;
private:
+ // Whether this is a dry run.
+ bool dryRun_;
+
// Used by GetInstance() only.
- Dumpstate();
+ Dumpstate(bool dryRun = false);
};
// for_each_pid_func = void (*)(int, const char*);
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
new file mode 100644
index 0000000..9e59080
--- /dev/null
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 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 "dumpstate.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <libgen.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+
+using ::testing::EndsWith;
+using ::testing::IsEmpty;
+using ::testing::StrEq;
+using ::testing::StartsWith;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+// Not used on test cases yet...
+void dumpstate_board(void) {
+}
+
+class DumpstateTest : public Test {
+ public:
+ void SetUp() {
+ SetDryRun(false);
+ }
+
+ // Runs a command and capture `stdout` and `stderr`.
+ int RunCommand(const std::string& title, const std::vector<std::string>& fullCommand,
+ const CommandOptions& options = CommandOptions::DEFAULT) {
+ CaptureStdout();
+ CaptureStderr();
+ int status = ds_.RunCommand(title, fullCommand, options);
+ out = GetCapturedStdout();
+ err = GetCapturedStderr();
+ return status;
+ }
+
+ // `stdout` and `stderr` from the last command ran.
+ std::string out, err;
+
+ std::string testPath = dirname(android::base::GetExecutablePath().c_str());
+ std::string simpleBin = testPath + "/../dumpstate_test_fixture/dumpstate_test_fixture";
+
+ void SetDryRun(bool dryRun) {
+ ds_.dryRun_ = dryRun;
+ }
+
+ private:
+ Dumpstate& ds_ = Dumpstate::GetInstance();
+};
+
+TEST_F(DumpstateTest, RunCommandNoArgs) {
+ EXPECT_EQ(-1, RunCommand("", {}));
+}
+
+TEST_F(DumpstateTest, RunCommandNoTitle) {
+ EXPECT_EQ(0, RunCommand("", {simpleBin}));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandWithTitle) {
+ EXPECT_EQ(0, RunCommand("I AM GROOT", {simpleBin}));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+ // We don't know the exact duration, so we check the prefix and suffix
+ EXPECT_THAT(out, StartsWith("------ I AM GROOT (" + simpleBin + ") ------\nstdout\n------"));
+ EXPECT_THAT(out, EndsWith("s was the duration of 'I AM GROOT' ------\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandRedirectStderr) {
+ EXPECT_EQ(
+ 0, RunCommand("", {simpleBin}, CommandOptions::WithTimeout(10).RedirectStderr().Build()));
+ EXPECT_THAT(out, IsEmpty());
+ EXPECT_THAT(err, StrEq("stderr\nstdout\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandWithOneArg) {
+ EXPECT_EQ(0, RunCommand("", {simpleBin, "one"}));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("one\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandWithNoArgs) {
+ EXPECT_EQ(0, RunCommand("", {simpleBin, "one", "is", "the", "loniest", "number"}));
+ EXPECT_THAT(err, IsEmpty());
+ EXPECT_THAT(out, StrEq("one is the loniest number\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandDryRun) {
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("I AM GROOT", {simpleBin}));
+ // We don't know the exact duration, so we check the prefix and suffix
+ EXPECT_THAT(out, StartsWith("------ I AM GROOT (" + simpleBin +
+ ") ------\n\t(skipped on dry run)\n------"));
+ EXPECT_THAT(out, EndsWith("s was the duration of 'I AM GROOT' ------\n"));
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, RunCommandDryRunNoTitle) {
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("", {simpleBin}));
+ EXPECT_THAT(out, IsEmpty());
+ EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, RunCommandDryRunAlways) {
+ SetDryRun(true);
+ EXPECT_EQ(0, RunCommand("", {simpleBin}, CommandOptions::WithTimeout(10).Always().Build()));
+ EXPECT_THAT(out, StrEq("stdout\n"));
+ EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+// TODO: add test for other scenarios:
+// - AsRoot()
+// - DropRoot
+// - WithLoggingMessage()
+// - command does not exist (invalid path)
+// - command times out
+// - command exits with a different value
+// - command is killed before timed out
+// - test progress
+
+// TODO: test DumpFile()
diff --git a/cmds/dumpstate/tests/dumpstate_test_fixture.cpp b/cmds/dumpstate/tests/dumpstate_test_fixture.cpp
new file mode 100644
index 0000000..5ac4cc7
--- /dev/null
+++ b/cmds/dumpstate/tests/dumpstate_test_fixture.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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 <stdio.h>
+
+/*
+ * Binary used to on RunCommand tests.
+ *
+ * Usage:
+ *
+ * - It returns 0 unless documented otherwise.
+ * - Without any arguments, prints `stdout\n` on `stdout` and `stderr\n` on `stderr`.
+ * - With arguments, prints all arguments on `stdout` separated by ` `.
+ */
+int main(int argc, char* const argv[]) {
+ if (argc > 1) {
+ for (int i = 1; i < argc; i++) {
+ fprintf(stdout, "%s", argv[i]);
+ if (i == argc - 1) {
+ fprintf(stdout, "\n");
+ } else {
+ fprintf(stdout, " ");
+ }
+ }
+ return 0;
+ }
+ fprintf(stdout, "stdout\n");
+ fprintf(stderr, "stderr\n");
+
+ return 0;
+}
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index e29d90d..55316c5 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -60,6 +60,9 @@
const CommandOptions& options = CommandOptions::DEFAULT) {
return ds.RunCommand(title, fullCommand, options);
}
+static bool IsDryRun() {
+ return Dumpstate::GetInstance().IsDryRun();
+}
/* list of native processes to include in the native dumps */
// This matches the /proc/pid/exe link instead of /proc/pid/cmdline.
@@ -154,11 +157,11 @@
return CommandOptions::CommandOptionsBuilder(timeout);
}
-Dumpstate::Dumpstate() {
+Dumpstate::Dumpstate(bool dryRun) : dryRun_(dryRun) {
}
Dumpstate& Dumpstate::GetInstance() {
- static Dumpstate sSingleton;
+ static Dumpstate sSingleton(android::base::GetBoolProperty("dumpstate.dry_run", false));
return sSingleton;
}
@@ -190,13 +193,16 @@
return (uint64_t) ts.tv_sec * NANOS_PER_SEC + ts.tv_nsec;
}
-// TODO: temporary function used during the C++ refactoring
-static bool is_dry_run() {
- return Dumpstate::GetInstance().IsDryRun();
+bool Dumpstate::IsDryRun() {
+ return dryRun_;
+}
+
+bool Dumpstate::IsUserBuild() {
+ return "user" == buildType_;
}
void for_each_userid(void (*func)(int), const char *header) {
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
DIR *d;
struct dirent *de;
@@ -279,7 +285,7 @@
}
void for_each_pid(for_each_pid_func func, const char *header) {
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
__for_each_pid(for_each_pid_helper, header, (void *) func);
}
@@ -333,13 +339,13 @@
}
void for_each_tid(for_each_tid_func func, const char *header) {
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
__for_each_pid(for_each_tid_helper, header, (void *) func);
}
void show_wchan(int pid, int tid, const char *name) {
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
char path[255];
char buffer[255];
@@ -406,7 +412,7 @@
}
void show_showtime(int pid, const char *name) {
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
char path[255];
char buffer[1023];
@@ -474,7 +480,7 @@
DurationReporter duration_reporter(title);
printf("------ %s ------\n", title);
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
/* Get size of kernel buffer */
int size = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
@@ -526,7 +532,7 @@
}
printf(") ------\n");
}
- if (is_dry_run()) {
+ if (IsDryRun()) {
update_progress(WEIGHT_FILE);
close(fd);
return 0;
@@ -627,7 +633,7 @@
if (!title.empty()) {
printf("------ %s (%s) ------\n", title.c_str(), dir);
}
- if (is_dry_run()) return 0;
+ if (IsDryRun()) return 0;
if (dir[strlen(dir) - 1] == '/') {
++slash;
@@ -684,7 +690,7 @@
* stuck.
*/
int dump_file_from_fd(const char *title, const char *path, int fd) {
- if (is_dry_run()) return 0;
+ if (IsDryRun()) return 0;
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {
@@ -796,7 +802,10 @@
MYLOGI(loggingMessage.c_str(), commandString.c_str());
}
- if (is_dry_run() && !options.Always()) {
+ if (IsDryRun() && !options.Always()) {
+ if (!title.empty()) {
+ printf("\t(skipped on dry run)\n");
+ }
update_progress(options.Timeout());
return 0;
}
@@ -988,7 +997,7 @@
const char* title = "SYSTEM PROPERTIES";
DurationReporter duration_reporter(title);
printf("------ %s ------\n", title);
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
size_t i;
num_props = 0;
property_list(print_prop, NULL);
@@ -1094,7 +1103,7 @@
/* dump Dalvik and native stack traces, return the trace file location (NULL if none) */
const char *dump_traces() {
DurationReporter duration_reporter("DUMP TRACES", nullptr);
- if (is_dry_run()) return nullptr;
+ if (IsDryRun()) return nullptr;
const char* result = nullptr;
@@ -1246,7 +1255,7 @@
void dump_route_tables() {
DurationReporter duration_reporter("DUMP ROUTE TABLES");
- if (is_dry_run()) return;
+ if (IsDryRun()) return;
const char* const RT_TABLES_PATH = "/data/misc/net/rt_tables";
ds.DumpFile("RT_TABLES", RT_TABLES_PATH);
FILE* fp = fopen(RT_TABLES_PATH, "re");