Moar Dumpstate refactoring and unit testing:

- Moves UpdateProgress() into Dumpstate object.
- Tests RunCommand.AsRoot().
- Tests RunCommand.DropRoot().
- Test update progress.

BUG: 26379932
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: I364d097487e090d201eb2e5f08fc794dce555510
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 27826a1..d2d5b40 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -26,8 +26,13 @@
 #include <thread>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
+#define LOG_TAG "dumpstate"
+#include <cutils/log.h>
+
 using ::testing::EndsWith;
 using ::testing::IsEmpty;
 using ::testing::StrEq;
@@ -46,6 +51,8 @@
   public:
     void SetUp() {
         SetDryRun(false);
+        SetBuildType(android::base::GetProperty("ro.build.type", "(unknown)"));
+        ds.updateProgress_ = false;
     }
 
     // Runs a command and capture `stdout` and `stderr`.
@@ -59,6 +66,54 @@
         return status;
     }
 
+    void SetDryRun(bool dryRun) {
+        ALOGD("Setting dryRun_ to %s\n", dryRun ? "true" : "false");
+        ds.dryRun_ = dryRun;
+    }
+
+    void SetBuildType(const std::string& buildType) {
+        ALOGD("Setting buildType_ to '%s'\n", buildType.c_str());
+        ds.buildType_ = buildType;
+    }
+
+    bool IsUserBuild() {
+        return "user" == android::base::GetProperty("ro.build.type", "(unknown)");
+    }
+
+    void DropRoot() {
+        drop_root_user();
+        uid_t uid = getuid();
+        ASSERT_EQ(2000, (int)uid);
+    }
+
+    // TODO: remove when progress is set by Binder callbacks.
+    void AssertSystemProperty(const std::string& key, const std::string& expectedValue) {
+        std::string actualValue = android::base::GetProperty(key, "not set");
+        EXPECT_THAT(expectedValue, StrEq(actualValue)) << "invalid value for property " << key;
+    }
+
+    std::string GetProgressMessage(int progress, int weightTotal, int oldWeightTotal = 0) {
+        EXPECT_EQ(progress, ds.progress_) << "invalid progress";
+        EXPECT_EQ(weightTotal, ds.weightTotal_) << "invalid weightTotal";
+
+        AssertSystemProperty(android::base::StringPrintf("dumpstate.%d.progress", getpid()),
+                             std::to_string(progress));
+
+        bool maxIncreased = oldWeightTotal > 0;
+
+        std::string adjustmentMessage = "";
+        if (maxIncreased) {
+            AssertSystemProperty(android::base::StringPrintf("dumpstate.%d.max", getpid()),
+                                 std::to_string(weightTotal));
+            adjustmentMessage = android::base::StringPrintf(
+                "Adjusting total weight from %d to %d\n", oldWeightTotal, weightTotal);
+        }
+
+        return android::base::StringPrintf("%sSetting progress (dumpstate.%d.progress): %d/%d\n",
+                                           adjustmentMessage.c_str(), getpid(), progress,
+                                           weightTotal);
+    }
+
     // `stdout` and `stderr` from the last command ran.
     std::string out, err;
 
@@ -67,10 +122,6 @@
     std::string echoCommand = "/system/bin/echo";
 
     Dumpstate& ds = Dumpstate::GetInstance();
-
-    void SetDryRun(bool dryRun) {
-        ds.dryRun_ = dryRun;
-    }
 };
 
 TEST_F(DumpstateTest, RunCommandNoArgs) {
@@ -213,9 +264,80 @@
                            " --pid --sleep 20' failed: killed by signal 15\n"));
 }
 
-// TODO: add test for other scenarios:
-// - AsRoot()
-// - DropRoot
-// - test progress
+TEST_F(DumpstateTest, RunCommandProgress) {
+    ds.updateProgress_ = true;
+    ds.weightTotal_ = 30;
+
+    EXPECT_EQ(0, RunCommand("", {simpleCommand}, CommandOptions::WithTimeout(20).Build()));
+    std::string progressMessage = GetProgressMessage(20, 30);
+    EXPECT_THAT(out, StrEq("stdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n" + progressMessage));
+
+    EXPECT_EQ(0, RunCommand("", {simpleCommand}, CommandOptions::WithTimeout(10).Build()));
+    progressMessage = GetProgressMessage(30, 30);
+    EXPECT_THAT(out, StrEq("stdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n" + progressMessage));
+
+    // Run a command that will increase maximum timeout.
+    EXPECT_EQ(0, RunCommand("", {simpleCommand}, CommandOptions::WithTimeout(1).Build()));
+    progressMessage = GetProgressMessage(31, 36, 30);  // 20% increase
+    EXPECT_THAT(out, StrEq("stdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n" + progressMessage));
+
+    // Make sure command ran while in dryRun is counted.
+    SetDryRun(true);
+    EXPECT_EQ(0, RunCommand("", {simpleCommand}, CommandOptions::WithTimeout(4).Build()));
+    progressMessage = GetProgressMessage(35, 36);
+    EXPECT_THAT(out, IsEmpty());
+    EXPECT_THAT(err, StrEq(progressMessage));
+}
+
+TEST_F(DumpstateTest, RunCommandDropRoot) {
+    // First check root case - only available when running with 'adb root'.
+    uid_t uid = getuid();
+    if (uid == 0) {
+        EXPECT_EQ(0, RunCommand("", {simpleCommand, "--uid"}));
+        EXPECT_THAT(out, StrEq("0\nstdout\n"));
+        EXPECT_THAT(err, StrEq("stderr\n"));
+        return;
+    }
+    // Then drop root.
+
+    EXPECT_EQ(0, RunCommand("", {simpleCommand, "--uid"},
+                            CommandOptions::WithTimeout(1).DropRoot().Build()));
+    EXPECT_THAT(out, StrEq("2000\nstdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n"));
+}
+
+TEST_F(DumpstateTest, RunCommandAsRootUserBuild) {
+    if (!IsUserBuild()) {
+        // Emulates user build if necessarily.
+        SetBuildType("user");
+    }
+
+    DropRoot();
+
+    EXPECT_EQ(0, RunCommand("", {simpleCommand}, CommandOptions::WithTimeout(1).AsRoot().Build()));
+
+    // We don't know the exact path of su, so we just check for the 'root ...' commands
+    EXPECT_THAT(out, StartsWith("Skipping"));
+    EXPECT_THAT(out, EndsWith("root " + simpleCommand + "' on user build.\n"));
+    EXPECT_THAT(err, IsEmpty());
+}
+
+TEST_F(DumpstateTest, RunCommandAsRootNonUserBuild) {
+    if (IsUserBuild()) {
+        ALOGI("Skipping RunCommandAsRootNonUserBuild on user builds\n");
+        return;
+    }
+
+    DropRoot();
+
+    EXPECT_EQ(0, RunCommand("", {simpleCommand, "--uid"},
+                            CommandOptions::WithTimeout(1).AsRoot().Build()));
+
+    EXPECT_THAT(out, StrEq("0\nstdout\n"));
+    EXPECT_THAT(err, StrEq("stderr\n"));
+}
 
 // TODO: test DumpFile()