Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a61c1e1b authored by Nandana Dutt's avatar Nandana Dutt Committed by android-build-merger
Browse files

Merge "Refactor options parsing in dumpstate" am: 22c321c7

am: 81e546fd

Change-Id: If88861f4c14b107b0a8f20cbb783145df0c51f33
parents 34ca82f9 81e546fd
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -133,6 +133,7 @@ cc_test {
    name: "dumpstate_test",
    defaults: ["dumpstate_defaults"],
    srcs: [
        "dumpstate.cpp",
        "tests/dumpstate_test.cpp",
    ],
    static_libs: ["libgmock"],
+133 −119
Original line number Diff line number Diff line
@@ -1751,131 +1751,140 @@ static void Vibrate(int duration_ms) {
    // clang-format on
}

/** Main entry point for dumpstate. */
int run_main(int argc, char* argv[]) {
    int do_add_date = 0;
    int do_zip_file = 0;
    int do_vibrate = 1;
    char* use_outfile = nullptr;
    int use_socket = 0;
    int use_control_socket = 0;
    int do_fb = 0;
    int do_broadcast = 0;
    int is_remote_mode = 0;
    bool show_header_only = false;
    bool do_start_service = false;
    bool telephony_only = false;
    bool wifi_only = false;
    int dup_stdout_fd;
    int dup_stderr_fd;

    /* set as high priority, and protect from OOM killer */
    setpriority(PRIO_PROCESS, 0, -20);

    FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we");
    if (oom_adj) {
        fputs("-1000", oom_adj);
        fclose(oom_adj);
    } else {
        /* fallback to kernels <= 2.6.35 */
        oom_adj = fopen("/proc/self/oom_adj", "we");
        if (oom_adj) {
            fputs("-17", oom_adj);
            fclose(oom_adj);
        }
    }

    /* parse arguments */
int Dumpstate::ParseCommandlineOptions(int argc, char* argv[]) {
    int ret = -1;  // success
    int c;
    while ((c = getopt(argc, argv, "dho:svqzpPBRSV:")) != -1) {
        switch (c) {
            // clang-format off
            case 'd': do_add_date = 1;            break;
            case 'z': do_zip_file = 1;            break;
            case 'o': use_outfile = optarg;       break;
            case 's': use_socket = 1;             break;
            case 'S': use_control_socket = 1;     break;
            case 'v': show_header_only = true;    break;
            case 'q': do_vibrate = 0;             break;
            case 'p': do_fb = 1;                  break;
            case 'P': ds.update_progress_ = true; break;
            case 'R': is_remote_mode = 1;         break;
            case 'B': do_broadcast = 1;           break;
            case 'd': options_.do_add_date = true;            break;
            case 'z': options_.do_zip_file = true;            break;
            case 'o': options_.use_outfile = optarg;          break;
            case 's': options_.use_socket = true;             break;
            case 'S': options_.use_control_socket = true;     break;
            case 'v': options_.show_header_only = true;       break;
            case 'q': options_.do_vibrate = false;            break;
            case 'p': options_.do_fb = true;                  break;
            case 'P': update_progress_ = true;                break;
            case 'R': options_.is_remote_mode = true;         break;
            case 'B': options_.do_broadcast = true;           break;
            case 'V':                                         break;  // compatibility no-op
            case 'h':
                ShowUsageAndExit(0);
                ret = 0;
                break;
            default:
                fprintf(stderr, "Invalid option: %c\n", c);
                ShowUsageAndExit();
                ret = 1;
                break;
                // clang-format on
        }
    }

    // TODO: use helper function to convert argv into a string
    for (int i = 0; i < argc; i++) {
        ds.args_ += argv[i];
        args_ += argv[i];
        if (i < argc - 1) {
            ds.args_ += " ";
            args_ += " ";
        }
    }

    ds.extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, "");
    if (!ds.extra_options_.empty()) {
    // Reset next index used by getopt so this can be called multiple times, for eg, in tests.
    optind = 1;
    return ret;
}

// TODO: Move away from system properties when we have binder.
void Dumpstate::SetOptionsFromProperties() {
    extra_options_ = android::base::GetProperty(PROPERTY_EXTRA_OPTIONS, "");
    if (!extra_options_.empty()) {
        // Framework uses a system property to override some command-line args.
        // Currently, it contains the type of the requested bugreport.
        if (ds.extra_options_ == "bugreportplus") {
        if (extra_options_ == "bugreportplus") {
            // Currently, the dumpstate binder is only used by Shell to update progress.
            do_start_service = true;
            ds.update_progress_ = true;
            do_fb = 0;
        } else if (ds.extra_options_ == "bugreportremote") {
            do_vibrate = 0;
            is_remote_mode = 1;
            do_fb = 0;
        } else if (ds.extra_options_ == "bugreportwear") {
            do_start_service = true;
            ds.update_progress_ = true;
            do_zip_file = 1;
        } else if (ds.extra_options_ == "bugreporttelephony") {
            telephony_only = true;
        } else if (ds.extra_options_ == "bugreportwifi") {
            wifi_only = true;
            do_zip_file = 1;
            options_.do_start_service = true;
            update_progress_ = true;
            options_.do_fb = false;
        } else if (extra_options_ == "bugreportremote") {
            options_.do_vibrate = false;
            options_.is_remote_mode = true;
            options_.do_fb = false;
        } else if (extra_options_ == "bugreportwear") {
            options_.do_start_service = true;
            update_progress_ = true;
            options_.do_zip_file = true;
        } else if (extra_options_ == "bugreporttelephony") {
            options_.telephony_only = true;
        } else if (extra_options_ == "bugreportwifi") {
            options_.wifi_only = true;
            options_.do_zip_file = true;
        } else {
            MYLOGE("Unknown extra option: %s\n", ds.extra_options_.c_str());
            MYLOGE("Unknown extra option: %s\n", extra_options_.c_str());
        }
        // Reset the property
        android::base::SetProperty(PROPERTY_EXTRA_OPTIONS, "");
    }

    ds.notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, "");
    if (!ds.notification_title.empty()) {
    notification_title = android::base::GetProperty(PROPERTY_EXTRA_TITLE, "");
    if (!notification_title.empty()) {
        // Reset the property
        android::base::SetProperty(PROPERTY_EXTRA_TITLE, "");

        ds.notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
        if (!ds.notification_description.empty()) {
        notification_description = android::base::GetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
        if (!notification_description.empty()) {
            // Reset the property
            android::base::SetProperty(PROPERTY_EXTRA_DESCRIPTION, "");
        }
        MYLOGD("notification (title:  %s, description: %s)\n",
               ds.notification_title.c_str(), ds.notification_description.c_str());
        MYLOGD("notification (title:  %s, description: %s)\n", notification_title.c_str(),
               notification_description.c_str());
    }
}

    if ((do_zip_file || do_add_date || ds.update_progress_ || do_broadcast) && !use_outfile) {
        ExitOnInvalidArgs();
bool Dumpstate::ValidateOptions() {
    if ((options_.do_zip_file || options_.do_add_date || ds.update_progress_ ||
         options_.do_broadcast) &&
        options_.use_outfile.empty()) {
        return false;
    }

    if (use_control_socket && !do_zip_file) {
        ExitOnInvalidArgs();
    if (options_.use_control_socket && !options_.do_zip_file) {
        return false;
    }

    if (ds.update_progress_ && !do_broadcast) {
        ExitOnInvalidArgs();
    if (ds.update_progress_ && !options_.do_broadcast) {
        return false;
    }

    if (options_.is_remote_mode && (ds.update_progress_ || !options_.do_broadcast ||
                                    !options_.do_zip_file || !options_.do_add_date)) {
        return false;
    }
    return true;
}

    if (is_remote_mode && (ds.update_progress_ || !do_broadcast || !do_zip_file || !do_add_date)) {
/* Main entry point for dumpstate. */
int run_main(int argc, char* argv[]) {
    /* set as high priority, and protect from OOM killer */
    setpriority(PRIO_PROCESS, 0, -20);

    FILE* oom_adj = fopen("/proc/self/oom_score_adj", "we");
    if (oom_adj) {
        fputs("-1000", oom_adj);
        fclose(oom_adj);
    } else {
        /* fallback to kernels <= 2.6.35 */
        oom_adj = fopen("/proc/self/oom_adj", "we");
        if (oom_adj) {
            fputs("-17", oom_adj);
            fclose(oom_adj);
        }
    }

    int status = ds.ParseCommandlineOptions(argc, argv);
    if (status != -1) {
        ShowUsageAndExit(status);
    }
    ds.SetOptionsFromProperties();
    if (!ds.ValidateOptions()) {
        ExitOnInvalidArgs();
    }

@@ -1890,17 +1899,20 @@ int run_main(int argc, char* argv[]) {
        exit(1);
    }

    if (show_header_only) {
    // TODO: make const reference, but first avoid setting do_zip_file below.
    Dumpstate::DumpOptions& options = ds.options_;
    if (options.show_header_only) {
        ds.PrintHeader();
        exit(0);
    }

    /* redirect output if needed */
    bool is_redirecting = !use_socket && use_outfile;
    // Redirect output if needed
    bool is_redirecting = !options.use_socket && !options.use_outfile.empty();

    // TODO: temporarily set progress until it's part of the Dumpstate constructor
    std::string stats_path =
        is_redirecting ? android::base::StringPrintf("%s/dumpstate-stats.txt", dirname(use_outfile))
    std::string stats_path = is_redirecting
                                 ? android::base::StringPrintf("%s/dumpstate-stats.txt",
                                                               dirname(options.use_outfile.c_str()))
                                 : "";
    ds.progress_.reset(new Progress(stats_path));

@@ -1913,7 +1925,7 @@ int run_main(int argc, char* argv[]) {

    register_sig_handler();

    if (do_start_service) {
    if (options.do_start_service) {
        MYLOGI("Starting 'dumpstate' service\n");
        android::status_t ret;
        if ((ret = android::os::DumpstateService::Start()) != android::OK) {
@@ -1934,23 +1946,24 @@ int run_main(int argc, char* argv[]) {

    // If we are going to use a socket, do it as early as possible
    // to avoid timeouts from bugreport.
    if (use_socket) {
    if (options.use_socket) {
        redirect_to_socket(stdout, "dumpstate");
    }

    if (use_control_socket) {
    if (options.use_control_socket) {
        MYLOGD("Opening control socket\n");
        ds.control_socket_fd_ = open_socket("dumpstate");
        ds.update_progress_ = 1;
    }

    if (is_redirecting) {
        ds.bugreport_dir_ = dirname(use_outfile);
        ds.bugreport_dir_ = dirname(options.use_outfile.c_str());
        std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD");
        std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE");
        ds.base_name_ = android::base::StringPrintf("%s-%s-%s", basename(use_outfile),
        ds.base_name_ =
            android::base::StringPrintf("%s-%s-%s", basename(options.use_outfile.c_str()),
                                        device_name.c_str(), build_id.c_str());
        if (do_add_date) {
        if (options.do_add_date) {
            char date[80];
            strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_));
            ds.name_ = date;
@@ -1958,13 +1971,13 @@ int run_main(int argc, char* argv[]) {
            ds.name_ = "undated";
        }

        if (telephony_only) {
        if (options.telephony_only) {
            ds.base_name_ += "-telephony";
        } else if (wifi_only) {
        } else if (options.wifi_only) {
            ds.base_name_ += "-wifi";
        }

        if (do_fb) {
        if (options.do_fb) {
            ds.screenshot_path_ = ds.GetPath(".png");
        }
        ds.tmp_path_ = ds.GetPath(".tmp");
@@ -1980,14 +1993,14 @@ int run_main(int argc, char* argv[]) {
            ds.bugreport_dir_.c_str(), ds.base_name_.c_str(), ds.name_.c_str(),
            ds.log_path_.c_str(), ds.tmp_path_.c_str(), ds.screenshot_path_.c_str());

        if (do_zip_file) {
        if (options.do_zip_file) {
            ds.path_ = ds.GetPath(".zip");
            MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str());
            create_parent_dirs(ds.path_.c_str());
            ds.zip_file.reset(fopen(ds.path_.c_str(), "wb"));
            if (ds.zip_file == nullptr) {
                MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno));
                do_zip_file = 0;
                options.do_zip_file = false;
            } else {
                ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get()));
            }
@@ -1995,7 +2008,7 @@ int run_main(int argc, char* argv[]) {
        }

        if (ds.update_progress_) {
            if (do_broadcast) {
            if (options.do_broadcast) {
                // clang-format off

                std::vector<std::string> am_args = {
@@ -2008,7 +2021,7 @@ int run_main(int argc, char* argv[]) {
                // clang-format on
                SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
            }
            if (use_control_socket) {
            if (options.use_control_socket) {
                dprintf(ds.control_socket_fd_, "BEGIN:%s\n", ds.path_.c_str());
            }
        }
@@ -2021,11 +2034,11 @@ int run_main(int argc, char* argv[]) {
        fclose(cmdline);
    }

    if (do_vibrate) {
    if (options.do_vibrate) {
        Vibrate(150);
    }

    if (do_fb && ds.do_early_screenshot_) {
    if (options.do_fb && ds.do_early_screenshot_) {
        if (ds.screenshot_path_.empty()) {
            // should not have happened
            MYLOGE("INTERNAL ERROR: skipping early screenshot because path was not set\n");
@@ -2035,13 +2048,15 @@ int run_main(int argc, char* argv[]) {
        }
    }

    if (do_zip_file) {
    if (options.do_zip_file) {
        if (chown(ds.path_.c_str(), AID_SHELL, AID_SHELL)) {
            MYLOGE("Unable to change ownership of zip file %s: %s\n", ds.path_.c_str(),
                   strerror(errno));
        }
    }

    int dup_stdout_fd;
    int dup_stderr_fd;
    if (is_redirecting) {
        TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr)));
        redirect_to_file(stderr, const_cast<char*>(ds.log_path_.c_str()));
@@ -2068,10 +2083,10 @@ int run_main(int argc, char* argv[]) {
    // duration is logged into MYLOG instead.
    ds.PrintHeader();

    if (telephony_only) {
    if (options.telephony_only) {
        DumpstateTelephonyOnly();
        ds.DumpstateBoard();
    } else if (wifi_only) {
    } else if (options.wifi_only) {
        DumpstateWifiOnly();
    } else {
        // Dumps systrace right away, otherwise it will be filled with unnecessary events.
@@ -2130,8 +2145,7 @@ int run_main(int argc, char* argv[]) {
    }

    /* rename or zip the (now complete) .tmp file to its final location */
    if (use_outfile) {

    if (!options.use_outfile.empty()) {
        /* check if user changed the suffix using system properties */
        std::string name = android::base::GetProperty(
            android::base::StringPrintf("dumpstate.%d.name", ds.pid_), "");
@@ -2160,7 +2174,7 @@ int run_main(int argc, char* argv[]) {
        }

        bool do_text_file = true;
        if (do_zip_file) {
        if (options.do_zip_file) {
            if (!ds.FinishZipFile()) {
                MYLOGE("Failed to finish zip file; sending text bugreport instead\n");
                do_text_file = true;
@@ -2189,7 +2203,7 @@ int run_main(int argc, char* argv[]) {
                ds.path_.clear();
            }
        }
        if (use_control_socket) {
        if (options.use_control_socket) {
            if (do_text_file) {
                dprintf(ds.control_socket_fd_,
                        "FAIL:could not create zip file, check %s "
@@ -2202,7 +2216,7 @@ int run_main(int argc, char* argv[]) {
    }

    /* vibrate a few but shortly times to let user know it's finished */
    if (do_vibrate) {
    if (options.do_vibrate) {
        for (int i = 0; i < 3; i++) {
            Vibrate(75);
            usleep((75 + 50) * 1000);
@@ -2210,7 +2224,7 @@ int run_main(int argc, char* argv[]) {
    }

    /* tell activity manager we're done */
    if (do_broadcast) {
    if (options.do_broadcast) {
        if (!ds.path_.empty()) {
            MYLOGI("Final bugreport path: %s\n", ds.path_.c_str());
            // clang-format off
@@ -2224,7 +2238,7 @@ int run_main(int argc, char* argv[]) {
                 "--es", "android.intent.extra.DUMPSTATE_LOG", ds.log_path_
            };
            // clang-format on
            if (do_fb) {
            if (options.do_fb) {
                am_args.push_back("--es");
                am_args.push_back("android.intent.extra.SCREENSHOT");
                am_args.push_back(ds.screenshot_path_);
@@ -2239,7 +2253,7 @@ int run_main(int argc, char* argv[]) {
                    am_args.push_back(ds.notification_description);
                }
            }
            if (is_remote_mode) {
            if (options.is_remote_mode) {
                am_args.push_back("--es");
                am_args.push_back("android.intent.extra.REMOTE_BUGREPORT_HASH");
                am_args.push_back(SHA256_file_hash(ds.path_));
@@ -2262,7 +2276,7 @@ int run_main(int argc, char* argv[]) {
        TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr)));
    }

    if (use_control_socket && ds.control_socket_fd_ != -1) {
    if (options.use_control_socket && ds.control_socket_fd_ != -1) {
        MYLOGD("Closing control socket\n");
        close(ds.control_socket_fd_);
    }
+38 −1
Original line number Diff line number Diff line
@@ -290,14 +290,51 @@ class Dumpstate {
    /* Returns true if the current version supports priority dump feature. */
    bool CurrentVersionSupportsPriorityDumps() const;

    // TODO: initialize fields on constructor
    // TODO: revisit the return values later.
    /*
     * Parses commandline arguments and sets runtime options accordingly.
     *
     * Returns 0 or positive number if the caller should exit with returned value as
     * exit code, or returns -1 if caller should proceed with execution.
     */
    int ParseCommandlineOptions(int argc, char* argv[]);

    /* Sets runtime options from the system properties. */
    void SetOptionsFromProperties();

    /* Returns true if the options set so far are consistent. */
    bool ValidateOptions();

    // TODO: add update_progress_ & other options from DumpState.
    /*
     * Structure to hold options that determine the behavior of dumpstate.
     */
    struct DumpOptions {
        bool do_add_date = false;
        bool do_zip_file = false;
        bool do_vibrate = true;
        bool use_socket = false;
        bool use_control_socket = false;
        bool do_fb = false;
        bool do_broadcast = false;
        bool is_remote_mode = false;
        bool show_header_only = false;
        bool do_start_service = false;
        bool telephony_only = false;
        bool wifi_only = false;
        std::string use_outfile;
    };

    // TODO: initialize fields on constructor
    // dumpstate id - unique after each device reboot.
    uint32_t id_;

    // dumpstate pid
    pid_t pid_;

    // Runtime options.
    DumpOptions options_;

    // Whether progress updates should be published.
    bool update_progress_ = false;

+154 −0
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@ using ::testing::internal::CaptureStdout;
using ::testing::internal::GetCapturedStderr;
using ::testing::internal::GetCapturedStdout;

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

class DumpstateListenerMock : public IDumpstateListener {
  public:
    MOCK_METHOD1(onProgressUpdated, binder::Status(int32_t progress));
@@ -144,6 +146,7 @@ class DumpstateTest : public DumpstateBaseTest {
        ds.progress_.reset(new Progress());
        ds.update_progress_ = false;
        ds.update_progress_threshold_ = 0;
        ds.options_ = Dumpstate::DumpOptions();
    }

    // Runs a command and capture `stdout` and `stderr`.
@@ -201,6 +204,157 @@ class DumpstateTest : public DumpstateBaseTest {
    Dumpstate& ds = Dumpstate::GetInstance();
};

TEST_F(DumpstateTest, ParseCommandlineOptionsNone) {
    // clang-format off
    char* argv[] = {
        const_cast<char*>("dumpstate")
    };
    // clang-format on

    int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
    EXPECT_EQ(-1, ret);
    EXPECT_FALSE(ds.options_.do_add_date);
    EXPECT_FALSE(ds.options_.do_zip_file);
    EXPECT_EQ("", ds.options_.use_outfile);
    EXPECT_FALSE(ds.options_.use_socket);
    EXPECT_FALSE(ds.options_.use_control_socket);
    EXPECT_FALSE(ds.options_.show_header_only);
    EXPECT_TRUE(ds.options_.do_vibrate);
    EXPECT_FALSE(ds.options_.do_fb);
    EXPECT_FALSE(ds.update_progress_);
    EXPECT_FALSE(ds.options_.is_remote_mode);
    EXPECT_FALSE(ds.options_.do_broadcast);
}

TEST_F(DumpstateTest, ParseCommandlineOptionsPartial1) {
    // clang-format off
    char* argv[] = {
        const_cast<char*>("dumpstate"),
        const_cast<char*>("-d"),
        const_cast<char*>("-z"),
        const_cast<char*>("-o abc"),
        const_cast<char*>("-s"),
        const_cast<char*>("-S"),

    };
    // clang-format on
    int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
    EXPECT_EQ(-1, ret);
    EXPECT_TRUE(ds.options_.do_add_date);
    EXPECT_TRUE(ds.options_.do_zip_file);
    // TODO: Maybe we should trim the filename
    EXPECT_EQ(" abc", std::string(ds.options_.use_outfile));
    EXPECT_TRUE(ds.options_.use_socket);
    EXPECT_TRUE(ds.options_.use_control_socket);

    // Other options retain default values
    EXPECT_FALSE(ds.options_.show_header_only);
    EXPECT_TRUE(ds.options_.do_vibrate);
    EXPECT_FALSE(ds.options_.do_fb);
    EXPECT_FALSE(ds.update_progress_);
    EXPECT_FALSE(ds.options_.is_remote_mode);
    EXPECT_FALSE(ds.options_.do_broadcast);
}

TEST_F(DumpstateTest, ParseCommandlineOptionsPartial2) {
    // clang-format off
    char* argv[] = {
        const_cast<char*>("dumpstate"),
        const_cast<char*>("-v"),
        const_cast<char*>("-q"),
        const_cast<char*>("-p"),
        const_cast<char*>("-P"),
        const_cast<char*>("-R"),
        const_cast<char*>("-B"),
    };
    // clang-format on
    int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);
    EXPECT_EQ(-1, ret);
    EXPECT_TRUE(ds.options_.show_header_only);
    EXPECT_FALSE(ds.options_.do_vibrate);
    EXPECT_TRUE(ds.options_.do_fb);
    EXPECT_TRUE(ds.update_progress_);
    EXPECT_TRUE(ds.options_.is_remote_mode);
    EXPECT_TRUE(ds.options_.do_broadcast);

    // Other options retain default values
    EXPECT_FALSE(ds.options_.do_add_date);
    EXPECT_FALSE(ds.options_.do_zip_file);
    EXPECT_EQ("", ds.options_.use_outfile);
    EXPECT_FALSE(ds.options_.use_socket);
    EXPECT_FALSE(ds.options_.use_control_socket);
}

TEST_F(DumpstateTest, ParseCommandlineOptionsHelp) {
    // clang-format off
    char* argv[] = {
        const_cast<char*>("dumpstate"),
        const_cast<char*>("-h")
    };
    // clang-format on
    int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);

    // -h is for help. Caller exit with code = 0 after printing usage, so expect return = 0.
    EXPECT_EQ(0, ret);
}

TEST_F(DumpstateTest, ParseCommandlineOptionsUnknown) {
    // clang-format off
    char* argv[] = {
        const_cast<char*>("dumpstate"),
        const_cast<char*>("-u")  // unknown flag
    };
    // clang-format on
    int ret = ds.ParseCommandlineOptions(ARRAY_SIZE(argv), argv);

    // -u is unknown. Caller exit with code = 1 to show execution failure, after printing usage,
    // so expect return = 1.
    EXPECT_EQ(1, ret);
}

TEST_F(DumpstateTest, ValidateOptionsNeedOutfile1) {
    ds.options_.do_zip_file = true;
    EXPECT_FALSE(ds.ValidateOptions());
    ds.options_.use_outfile = "a/b/c";
    EXPECT_TRUE(ds.ValidateOptions());
}

TEST_F(DumpstateTest, ValidateOptionsNeedOutfile2) {
    ds.options_.do_broadcast = true;
    EXPECT_FALSE(ds.ValidateOptions());
    ds.options_.use_outfile = "a/b/c";
    EXPECT_TRUE(ds.ValidateOptions());
}

TEST_F(DumpstateTest, ValidateOptionsNeedZipfile) {
    ds.options_.use_control_socket = true;
    EXPECT_FALSE(ds.ValidateOptions());

    ds.options_.do_zip_file = true;
    ds.options_.use_outfile = "a/b/c";  // do_zip_file needs outfile
    EXPECT_TRUE(ds.ValidateOptions());
}

TEST_F(DumpstateTest, ValidateOptionsUpdateProgressNeedsBroadcast) {
    ds.update_progress_ = true;
    ds.options_.use_outfile = "a/b/c";  // update_progress_ needs outfile
    EXPECT_FALSE(ds.ValidateOptions());

    ds.options_.do_broadcast = true;
    EXPECT_TRUE(ds.ValidateOptions());
}

TEST_F(DumpstateTest, ValidateOptionsRemoteMode) {
    ds.options_.is_remote_mode = true;
    EXPECT_FALSE(ds.ValidateOptions());

    ds.options_.do_broadcast = true;
    ds.options_.do_zip_file = true;
    ds.options_.do_add_date = true;
    ds.options_.use_outfile = "a/b/c";  // do_broadcast needs outfile
    EXPECT_TRUE(ds.ValidateOptions());
}

TEST_F(DumpstateTest, RunCommandNoArgs) {
    EXPECT_EQ(-1, RunCommand("", {}));
}