Added tests for DumpFile.

BUG: 31807540
BUG: 30564705

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: Ide3a8d5f4b2c02b752ce6f6692c83b71ebdf62ed
diff --git a/cmds/dumpstate/Android.mk b/cmds/dumpstate/Android.mk
index 2eb512f..695e464 100644
--- a/cmds/dumpstate/Android.mk
+++ b/cmds/dumpstate/Android.mk
@@ -78,7 +78,23 @@
 
 LOCAL_CFLAGS := $(COMMON_LOCAL_CFLAGS)
 
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
 LOCAL_SRC_FILES := \
         tests/dumpstate_test_fixture.cpp
 
+dist_zip_root := $(TARGET_OUT_DATA)
+dumpstate_tests_subpath_from_data := nativetest/dumpstate_test_fixture
+dumpstate_tests_root_in_device := /data/$(dumpstate_tests_subpath_from_data)
+dumpstate_tests_root_for_test_zip := $(dist_zip_root)/$(dumpstate_tests_subpath_from_data)
+testdata_files := $(call find-subdir-files, testdata/*)
+
+GEN := $(addprefix $(dumpstate_tests_root_for_test_zip)/, $(testdata_files))
+$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
+$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
+$(GEN): $(dumpstate_tests_root_for_test_zip)/testdata/% : $(LOCAL_PATH)/testdata/%
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(GEN)
+LOCAL_PICKUP_FILES := $(dist_zip_root)
+
 include $(BUILD_NATIVE_TEST)
diff --git a/cmds/dumpstate/testdata/multiple-lines-with-newline.txt b/cmds/dumpstate/testdata/multiple-lines-with-newline.txt
new file mode 100644
index 0000000..7b7a187
--- /dev/null
+++ b/cmds/dumpstate/testdata/multiple-lines-with-newline.txt
@@ -0,0 +1,3 @@
+I AM LINE1
+I AM LINE2
+I AM LINE3
diff --git a/cmds/dumpstate/testdata/multiple-lines.txt b/cmds/dumpstate/testdata/multiple-lines.txt
new file mode 100644
index 0000000..bead103
--- /dev/null
+++ b/cmds/dumpstate/testdata/multiple-lines.txt
@@ -0,0 +1,3 @@
+I AM LINE1
+I AM LINE2
+I AM LINE3
\ No newline at end of file
diff --git a/cmds/dumpstate/testdata/single-line-with-newline.txt b/cmds/dumpstate/testdata/single-line-with-newline.txt
new file mode 100644
index 0000000..cb48c82
--- /dev/null
+++ b/cmds/dumpstate/testdata/single-line-with-newline.txt
@@ -0,0 +1 @@
+I AM LINE1
diff --git a/cmds/dumpstate/testdata/single-line.txt b/cmds/dumpstate/testdata/single-line.txt
new file mode 100644
index 0000000..2f64046
--- /dev/null
+++ b/cmds/dumpstate/testdata/single-line.txt
@@ -0,0 +1 @@
+I AM LINE1
\ No newline at end of file
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index d2d5b40..782230a 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -66,6 +66,16 @@
         return status;
     }
 
+    // Dumps a file and capture `stdout` and `stderr`.
+    int DumpFile(const std::string& title, const std::string& path) {
+        CaptureStdout();
+        CaptureStderr();
+        int status = ds.DumpFile(title, path);
+        out = GetCapturedStdout();
+        err = GetCapturedStderr();
+        return status;
+    }
+
     void SetDryRun(bool dryRun) {
         ALOGD("Setting dryRun_ to %s\n", dryRun ? "true" : "false");
         ds.dryRun_ = dryRun;
@@ -118,7 +128,9 @@
     std::string out, err;
 
     std::string testPath = dirname(android::base::GetExecutablePath().c_str());
-    std::string simpleCommand = testPath + "/../dumpstate_test_fixture/dumpstate_test_fixture";
+    std::string fixturesPath = testPath + "/../dumpstate_test_fixture/";
+    std::string testDataPath = fixturesPath + "/testdata/";
+    std::string simpleCommand = fixturesPath + "dumpstate_test_fixture";
     std::string echoCommand = "/system/bin/echo";
 
     Dumpstate& ds = Dumpstate::GetInstance();
@@ -266,6 +278,7 @@
 
 TEST_F(DumpstateTest, RunCommandProgress) {
     ds.updateProgress_ = true;
+    ds.progress_ = 0;
     ds.weightTotal_ = 30;
 
     EXPECT_EQ(0, RunCommand("", {simpleCommand}, CommandOptions::WithTimeout(20).Build()));
@@ -340,4 +353,72 @@
     EXPECT_THAT(err, StrEq("stderr\n"));
 }
 
-// TODO: test DumpFile()
+TEST_F(DumpstateTest, DumpFileNotFoundNoTitle) {
+    EXPECT_EQ(-1, DumpFile("", "/I/cant/believe/I/exist"));
+    EXPECT_THAT(out,
+                StrEq("*** Error dumping /I/cant/believe/I/exist: No such file or directory\n"));
+    EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, DumpFileNotFoundWithTitle) {
+    EXPECT_EQ(-1, DumpFile("Y U NO EXIST?", "/I/cant/believe/I/exist"));
+    EXPECT_THAT(err, IsEmpty());
+    // We don't know the exact duration, so we check the prefix and suffix
+    EXPECT_THAT(out, StartsWith("*** Error dumping /I/cant/believe/I/exist (Y U NO EXIST?): No "
+                                "such file or directory\n"));
+    EXPECT_THAT(out, EndsWith("s was the duration of 'Y U NO EXIST?' ------\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileSingleLine) {
+    EXPECT_EQ(0, DumpFile("", testDataPath + "single-line.txt"));
+    EXPECT_THAT(err, IsEmpty());
+    EXPECT_THAT(out, StrEq("I AM LINE1\n"));  // dumpstate adds missing newline
+}
+
+TEST_F(DumpstateTest, DumpFileSingleLineWithNewLine) {
+    EXPECT_EQ(0, DumpFile("", testDataPath + "single-line-with-newline.txt"));
+    EXPECT_THAT(err, IsEmpty());
+    EXPECT_THAT(out, StrEq("I AM LINE1\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileMultipleLines) {
+    EXPECT_EQ(0, DumpFile("", testDataPath + "multiple-lines.txt"));
+    EXPECT_THAT(err, IsEmpty());
+    EXPECT_THAT(out, StrEq("I AM LINE1\nI AM LINE2\nI AM LINE3\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileMultipleLinesWithNewLine) {
+    EXPECT_EQ(0, DumpFile("", testDataPath + "multiple-lines-with-newline.txt"));
+    EXPECT_THAT(err, IsEmpty());
+    EXPECT_THAT(out, StrEq("I AM LINE1\nI AM LINE2\nI AM LINE3\n"));
+}
+
+TEST_F(DumpstateTest, DumpFileOnDryRunNoTitle) {
+    SetDryRun(true);
+    EXPECT_EQ(0, DumpFile("", testDataPath + "single-line.txt"));
+    EXPECT_THAT(err, IsEmpty());
+    EXPECT_THAT(out, IsEmpty());
+}
+
+TEST_F(DumpstateTest, DumpFileOnDryRun) {
+    SetDryRun(true);
+    EXPECT_EQ(0, DumpFile("Might as well dump. Dump!", testDataPath + "single-line.txt"));
+    EXPECT_THAT(err, IsEmpty());
+    EXPECT_THAT(out, StartsWith("------ Might as well dump. Dump! (" + testDataPath +
+                                "single-line.txt) ------\n\t(skipped on dry run)\n------"));
+    EXPECT_THAT(out, EndsWith("s was the duration of 'Might as well dump. Dump!' ------\n"));
+    EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, DumpFileUpdateProgress) {
+    ds.updateProgress_ = true;
+    ds.progress_ = 0;
+    ds.weightTotal_ = 30;
+
+    EXPECT_EQ(0, DumpFile("", testDataPath + "single-line.txt"));
+
+    std::string progressMessage = GetProgressMessage(5, 30);  // TODO: unhardcode WEIGHT_FILE (5)?
+
+    EXPECT_THAT(err, StrEq(progressMessage));
+    EXPECT_THAT(out, StrEq("I AM LINE1\n"));  // dumpstate adds missing newline
+}
diff --git a/cmds/dumpstate/utils.cpp b/cmds/dumpstate/utils.cpp
index a9118b1..b7645b4 100644
--- a/cmds/dumpstate/utils.cpp
+++ b/cmds/dumpstate/utils.cpp
@@ -591,11 +591,23 @@
 
 int Dumpstate::DumpFile(const std::string& title, const std::string& path) {
     DurationReporter durationReporter(title);
+    if (IsDryRun()) {
+        if (!title.empty()) {
+            printf("------ %s (%s) ------\n", title.c_str(), path.c_str());
+            printf("\t(skipped on dry run)\n");
+        }
+        UpdateProgress(WEIGHT_FILE);
+        return 0;
+    }
+
     int fd = TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
     if (fd < 0) {
         int err = errno;
-        printf("*** %s: %s\n", path.c_str(), strerror(err));
-        if (!title.empty()) printf("\n");
+        if (title.empty()) {
+            printf("*** Error dumping %s: %s\n", path.c_str(), strerror(err));
+        } else {
+            printf("*** Error dumping %s (%s): %s\n", path.c_str(), title.c_str(), strerror(err));
+        }
         return -1;
     }
     return _dump_file_from_fd(title, path.c_str(), fd);