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

Commit 6f5080fa authored by Felipe Leme's avatar Felipe Leme Committed by Josh Gao
Browse files

DO NOT MERGE: Show bugreport progress.

adb calls bugreportz to generate a bugreport; initially, bugreportz
would only report the final status of the operation (OK or FAIL), but
now it sends intermediate PROGRESS lines reporting its progress (in the
form of current/max).

Similarly, the initial implementation of 'adb bugreport <zip_file>'
would print an initial 'please wait' message and wait for the full
stdout before parsing the result, but now it uses a new callback class
to handle the stdout as it is generated by bugreportz.

BUG: 28609499

Change-Id: I6644fc39a686279e1635f946a47f3847b547d1c1
(cherry picked from commit cd42d658)
(cherry picked from commit 97b73a0d)
parent 0d4f0508
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -187,6 +187,7 @@ LOCAL_SRC_FILES := \
    adb_client.cpp \
    adb_client.cpp \
    bugreport.cpp \
    bugreport.cpp \
    bugreport_test.cpp \
    bugreport_test.cpp \
    line_printer.cpp \
    services.cpp \
    services.cpp \
    shell_service_protocol.cpp \
    shell_service_protocol.cpp \
    shell_service_protocol_test.cpp \
    shell_service_protocol_test.cpp \
+102 −27
Original line number Original line Diff line number Diff line
@@ -23,55 +23,92 @@


static constexpr char BUGZ_OK_PREFIX[] = "OK:";
static constexpr char BUGZ_OK_PREFIX[] = "OK:";
static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
static constexpr char BUGZ_PROGRESS_PREFIX[] = "PROGRESS:";
static constexpr char BUGZ_PROGRESS_SEPARATOR[] = "/";


// Custom callback used to handle the output of zipped bugreports.
// Custom callback used to handle the output of zipped bugreports.
class BugreportStandardStreamsCallback : public StandardStreamsCallbackInterface {
class BugreportStandardStreamsCallback : public StandardStreamsCallbackInterface {
  public:
  public:
    BugreportStandardStreamsCallback(const std::string& dest_file, Bugreport* br)
    BugreportStandardStreamsCallback(const std::string& dest_file, bool show_progress, Bugreport* br)
        : br_(br), dest_file_(dest_file), stdout_str_() {
        : br_(br), dest_file_(dest_file), show_progress_(show_progress), status_(-1), line_() {
    }
    }


    void OnStdout(const char* buffer, int length) {
    void OnStdout(const char* buffer, int length) {
        std::string output;
        for (int i = 0; i < length; i++) {
        OnStream(&output, stdout, buffer, length);
            char c = buffer[i];
        stdout_str_.append(output);
            if (c == '\n') {
                ProcessLine(line_);
                line_.clear();
            } else {
                line_.append(1, c);
            }
        }
    }
    }


    void OnStderr(const char* buffer, int length) {
    void OnStderr(const char* buffer, int length) {
        OnStream(nullptr, stderr, buffer, length);
        OnStream(nullptr, stderr, buffer, length);
    }
    }

    int Done(int unused_) {
    int Done(int unused_) {
        int status = -1;
        // Process remaining line, if any...
        std::string output = android::base::Trim(stdout_str_);
        ProcessLine(line_);
        if (android::base::StartsWith(output, BUGZ_OK_PREFIX)) {
        // ..then return.
            const char* zip_file = &output[strlen(BUGZ_OK_PREFIX)];
        return status_;
    }

  private:
    void ProcessLine(const std::string& line) {
        if (line.empty()) return;

        if (android::base::StartsWith(line, BUGZ_OK_PREFIX)) {
            if (show_progress_) {
                // Make sure pull message doesn't conflict with generation message.
                br_->UpdateProgress(dest_file_, 100, 100, true);
            }

            const char* zip_file = &line[strlen(BUGZ_OK_PREFIX)];
            std::vector<const char*> srcs{zip_file};
            std::vector<const char*> srcs{zip_file};
            status = br_->DoSyncPull(srcs, dest_file_.c_str(), true, dest_file_.c_str()) ? 0 : 1;
            status_ = br_->DoSyncPull(srcs, dest_file_.c_str(), true, dest_file_.c_str()) ? 0 : 1;
            if (status != 0) {
            if (status_ != 0) {
                fprintf(stderr, "Could not copy file '%s' to '%s'\n", zip_file, dest_file_.c_str());
                fprintf(stderr, "Could not copy file '%s' to '%s'\n", zip_file, dest_file_.c_str());
            }
            }
        } else if (android::base::StartsWith(output, BUGZ_FAIL_PREFIX)) {
        } else if (android::base::StartsWith(line, BUGZ_FAIL_PREFIX)) {
            const char* error_message = &output[strlen(BUGZ_FAIL_PREFIX)];
            const char* error_message = &line[strlen(BUGZ_FAIL_PREFIX)];
            fprintf(stderr, "Device failed to take a zipped bugreport: %s\n", error_message);
            fprintf(stderr, "Device failed to take a zipped bugreport: %s\n", error_message);
            status_ = -1;
        } else if (show_progress_ && android::base::StartsWith(line, BUGZ_PROGRESS_PREFIX)) {
            // progress_line should have the following format:
            //
            // BUGZ_PROGRESS_PREFIX:PROGRESS/TOTAL
            //
            size_t idx1 = line.rfind(BUGZ_PROGRESS_PREFIX) + strlen(BUGZ_PROGRESS_PREFIX);
            size_t idx2 = line.rfind(BUGZ_PROGRESS_SEPARATOR);
            int progress = std::stoi(line.substr(idx1, (idx2 - idx1)));
            int total = std::stoi(line.substr(idx2 + 1));
            br_->UpdateProgress(dest_file_, progress, total);
        } else {
        } else {
            fprintf(stderr,
            fprintf(stderr,
                    "Unexpected string (%s) returned by bugreportz, "
                    "WARNING: unexpected line (%s) returned by bugreportz, "
                    "device probably does not support it\n",
                    "device probably does not support zipped bugreports.\n"
                    output.c_str());
                    "Try 'adb bugreport' instead.",
                    line.c_str());
        }
        }

        return status;
    }
    }


  private:
    Bugreport* br_;
    Bugreport* br_;
    const std::string dest_file_;
    const std::string dest_file_;
    std::string stdout_str_;
    bool show_progress_;
    int status_;

    // Temporary buffer containing the characters read since the last newline
    // (\n).
    std::string line_;


    DISALLOW_COPY_AND_ASSIGN(BugreportStandardStreamsCallback);
    DISALLOW_COPY_AND_ASSIGN(BugreportStandardStreamsCallback);
};
};


// Implemented in commandline.cpp
int usage();

int Bugreport::DoIt(TransportType transport_type, const char* serial, int argc, const char** argv) {
int Bugreport::DoIt(TransportType transport_type, const char* serial, int argc, const char** argv) {
    if (argc == 1) return SendShellCommand(transport_type, serial, "bugreport", false);
    if (argc == 1) return SendShellCommand(transport_type, serial, "bugreport", false);
    if (argc != 2) return usage();
    if (argc != 2) return usage();
@@ -84,12 +121,50 @@ int Bugreport::DoIt(TransportType transport_type, const char* serial, int argc,
        // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
        // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
        dest_file += ".zip";
        dest_file += ".zip";
    }
    }

    // Gets bugreportz version.
    std::string bugz_stderr;
    DefaultStandardStreamsCallback version_callback(nullptr, &bugz_stderr);
    int status = SendShellCommand(transport_type, serial, "bugreportz -v", false, &version_callback);

    if (status != 0) {
        fprintf(stderr,
                "Failed to get bugreportz version: 'bugreport -v' returned '%s' "
                "(code %d)."
                "\nIf the device does not support it, try running 'adb bugreport' "
                "to get a "
                "flat-file bugreport.",
                bugz_stderr.c_str(), status);
        return status;
    }
    std::string bugz_version = android::base::Trim(bugz_stderr);

    bool show_progress = true;
    std::string bugz_command = "bugreportz -p";
    if (bugz_version == "1.0") {
        // 1.0 does not support progress notifications, so print a disclaimer
        // message instead.
        fprintf(stderr,
        fprintf(stderr,
                "Bugreport is in progress and it could take minutes to complete.\n"
                "Bugreport is in progress and it could take minutes to complete.\n"
            "Please be patient and do not cancel or disconnect your device until "
                "Please be patient and do not cancel or disconnect your device "
            "it completes.\n");
                "until it completes."
    BugreportStandardStreamsCallback bugz_callback(dest_file, this);
                "\n");
    return SendShellCommand(transport_type, serial, "bugreportz", false, &bugz_callback);
        show_progress = false;
        bugz_command = "bugreportz";
    }
    BugreportStandardStreamsCallback bugz_callback(dest_file, show_progress, this);
    return SendShellCommand(transport_type, serial, bugz_command, false, &bugz_callback);
}

void Bugreport::UpdateProgress(const std::string& file_name, int progress, int total,
                               bool keep_info_line) {
    int progress_percentage = (progress * 100 / total);
    line_printer_.Print(android::base::StringPrintf("[%3d%%] generating %s", progress_percentage,
                                                    file_name.c_str()),
                        LinePrinter::INFO);
    if (keep_info_line) {
        line_printer_.KeepInfoLine();
    }
}
}


int Bugreport::SendShellCommand(TransportType transport_type, const char* serial,
int Bugreport::SendShellCommand(TransportType transport_type, const char* serial,
+5 −1
Original line number Original line Diff line number Diff line
@@ -21,12 +21,13 @@


#include "adb.h"
#include "adb.h"
#include "commandline.h"
#include "commandline.h"
#include "line_printer.h"


class Bugreport {
class Bugreport {
    friend class BugreportStandardStreamsCallback;
    friend class BugreportStandardStreamsCallback;


  public:
  public:
    Bugreport() {
    Bugreport() : line_printer_() {
    }
    }
    int DoIt(TransportType transport_type, const char* serial, int argc, const char** argv);
    int DoIt(TransportType transport_type, const char* serial, int argc, const char** argv);


@@ -42,6 +43,9 @@ class Bugreport {
                            const char* name);
                            const char* name);


  private:
  private:
    virtual void UpdateProgress(const std::string& file_name, int progress, int total,
                                bool keep_info_line = false);
    LinePrinter line_printer_;
    DISALLOW_COPY_AND_ASSIGN(Bugreport);
    DISALLOW_COPY_AND_ASSIGN(Bugreport);
};
};


+121 −29
Original line number Original line Diff line number Diff line
@@ -51,37 +51,63 @@ int send_shell_command(TransportType transport_type, const char* serial, const s
    return -42;
    return -42;
}
}


enum StreamType {
    kStreamStdout,
    kStreamStderr,
};

// gmock black magic to provide a WithArg<4>(WriteOnStdout(output)) matcher
// gmock black magic to provide a WithArg<4>(WriteOnStdout(output)) matcher
typedef void OnStandardStreamsCallbackFunction(StandardStreamsCallbackInterface*);
typedef void OnStandardStreamsCallbackFunction(StandardStreamsCallbackInterface*);


class OnStandardStreamsCallbackAction : public ActionInterface<OnStandardStreamsCallbackFunction> {
class OnStandardStreamsCallbackAction : public ActionInterface<OnStandardStreamsCallbackFunction> {
  public:
  public:
    explicit OnStandardStreamsCallbackAction(const std::string& output) : output_(output) {
    explicit OnStandardStreamsCallbackAction(StreamType type, const std::string& output)
        : type_(type), output_(output) {
    }
    }
    virtual Result Perform(const ArgumentTuple& args) {
    virtual Result Perform(const ArgumentTuple& args) {
        if (type_ == kStreamStdout) {
            ::std::tr1::get<0>(args)->OnStdout(output_.c_str(), output_.size());
            ::std::tr1::get<0>(args)->OnStdout(output_.c_str(), output_.size());
        }
        }
        if (type_ == kStreamStderr) {
            ::std::tr1::get<0>(args)->OnStderr(output_.c_str(), output_.size());
        }
    }


  private:
  private:
    StreamType type_;
    std::string output_;
    std::string output_;
};
};


// Matcher used to emulated StandardStreamsCallbackInterface.OnStdout(buffer,
// length)
Action<OnStandardStreamsCallbackFunction> WriteOnStdout(const std::string& output) {
Action<OnStandardStreamsCallbackFunction> WriteOnStdout(const std::string& output) {
    return MakeAction(new OnStandardStreamsCallbackAction(output));
    return MakeAction(new OnStandardStreamsCallbackAction(kStreamStdout, output));
}

// Matcher used to emulated StandardStreamsCallbackInterface.OnStderr(buffer,
// length)
Action<OnStandardStreamsCallbackFunction> WriteOnStderr(const std::string& output) {
    return MakeAction(new OnStandardStreamsCallbackAction(kStreamStderr, output));
}
}


typedef int CallbackDoneFunction(StandardStreamsCallbackInterface*);
typedef int CallbackDoneFunction(StandardStreamsCallbackInterface*);


class CallbackDoneAction : public ActionInterface<CallbackDoneFunction> {
class CallbackDoneAction : public ActionInterface<CallbackDoneFunction> {
  public:
  public:
    explicit CallbackDoneAction(int status) : status_(status) {
    }
    virtual Result Perform(const ArgumentTuple& args) {
    virtual Result Perform(const ArgumentTuple& args) {
        int status = ::std::tr1::get<0>(args)->Done(123);  // Value passed does not matter
        int status = ::std::tr1::get<0>(args)->Done(status_);
        return status;
        return status;
    }
    }

  private:
    int status_;
};
};


Action<CallbackDoneFunction> ReturnCallbackDone() {
// Matcher used to emulated StandardStreamsCallbackInterface.Done(status)
    return MakeAction(new CallbackDoneAction());
Action<CallbackDoneFunction> ReturnCallbackDone(int status = -1337) {
    return MakeAction(new CallbackDoneAction(status));
}
}


class BugreportMock : public Bugreport {
class BugreportMock : public Bugreport {
@@ -91,10 +117,22 @@ class BugreportMock : public Bugreport {
                     bool disable_shell_protocol, StandardStreamsCallbackInterface* callback));
                     bool disable_shell_protocol, StandardStreamsCallbackInterface* callback));
    MOCK_METHOD4(DoSyncPull, bool(const std::vector<const char*>& srcs, const char* dst,
    MOCK_METHOD4(DoSyncPull, bool(const std::vector<const char*>& srcs, const char* dst,
                                  bool copy_attrs, const char* name));
                                  bool copy_attrs, const char* name));
    MOCK_METHOD4(UpdateProgress, void(const std::string&, int, int, bool));
};
};


class BugreportTest : public ::testing::Test {
class BugreportTest : public ::testing::Test {
  public:
  public:
    void SetBugreportzVersion(const std::string& version) {
        EXPECT_CALL(br_,
                    SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
            .WillOnce(DoAll(WithArg<4>(WriteOnStderr(version.c_str())),
                            WithArg<4>(ReturnCallbackDone(0))));
    }

    void ExpectProgress(int progress, int total, bool keep_info_line = false) {
        EXPECT_CALL(br_, UpdateProgress(StrEq("file.zip"), progress, total, keep_info_line));
    }

    BugreportMock br_;
    BugreportMock br_;
};
};


@@ -113,8 +151,10 @@ TEST_F(BugreportTest, FlatFileFormat) {
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
}
}


// Tests 'adb bugreport file.zip' when it succeeds
// Tests 'adb bugreport file.zip' when it succeeds and device does not support
TEST_F(BugreportTest, Ok) {
// progress.
TEST_F(BugreportTest, OkLegacy) {
    SetBugreportzVersion("1.0");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
                        WithArg<4>(ReturnCallbackDone())));
                        WithArg<4>(ReturnCallbackDone())));
@@ -126,25 +166,46 @@ TEST_F(BugreportTest, Ok) {
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}
}


// Tests 'adb bugreport file' when it succeeds
// Tests 'adb bugreport file.zip' when it succeeds but response was sent in
TEST_F(BugreportTest, OkNoExtension) {
// multiple buffer writers and without progress updates.
TEST_F(BugreportTest, OkLegacySplitBuffer) {
    SetBugreportzVersion("1.0");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device")),
                        WithArg<4>(WriteOnStdout("/bugreport.zip")),
                        WithArg<4>(ReturnCallbackDone())));
                        WithArg<4>(ReturnCallbackDone())));
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
                                true, StrEq("file.zip")))
                                true, StrEq("file.zip")))
        .WillOnce(Return(true));
        .WillOnce(Return(true));


    const char* args[1024] = {"bugreport", "file"};
    const char* args[1024] = {"bugreport", "file.zip"};
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}
}


// Tests 'adb bugreport file.zip' when it succeeds but response was sent in
// Tests 'adb bugreport file.zip' when it succeeds and displays progress.
// multiple buffer writers.
TEST_F(BugreportTest, Ok) {
TEST_F(BugreportTest, OkSplitBuffer) {
    SetBugreportzVersion("1.1");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    ExpectProgress(1, 100);
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device")),
    ExpectProgress(10, 100);
                        WithArg<4>(WriteOnStdout("/bugreport.zip")),
    ExpectProgress(50, 100);
    ExpectProgress(99, 100);
    ExpectProgress(100, 100, true);
    // clang-format off
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(DoAll(
            // Progress line in one write
            WithArg<4>(WriteOnStdout("PROGRESS:1/100\n")),
            // Add some bogus lines
            WithArg<4>(WriteOnStdout("\nDUDE:SWEET\n\n")),
            // Multiple progress lines in one write
            WithArg<4>(WriteOnStdout("PROGRESS:10/100\nPROGRESS:50/100\n")),
            // Progress line in multiple writes
            WithArg<4>(WriteOnStdout("PROG")),
            WithArg<4>(WriteOnStdout("RESS:99")),
            WithArg<4>(WriteOnStdout("/100\n")),
            // Split last message as well, just in case
            WithArg<4>(WriteOnStdout("OK:/device/bugreport")),
            WithArg<4>(WriteOnStdout(".zip")),
            WithArg<4>(ReturnCallbackDone())));
            WithArg<4>(ReturnCallbackDone())));
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
                                true, StrEq("file.zip")))
                                true, StrEq("file.zip")))
@@ -154,35 +215,54 @@ TEST_F(BugreportTest, OkSplitBuffer) {
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}
}


// Tests 'adb bugreport file' when it succeeds
TEST_F(BugreportTest, OkNoExtension) {
    SetBugreportzVersion("1.1");
    ExpectProgress(100, 100, true);
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip\n")),
                        WithArg<4>(ReturnCallbackDone())));
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
                                true, StrEq("file.zip")))
        .WillOnce(Return(true));

    const char* args[1024] = {"bugreport", "file"};
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}

// Tests 'adb bugreport file.zip' when the bugreport itself failed
// Tests 'adb bugreport file.zip' when the bugreport itself failed
TEST_F(BugreportTest, BugreportzReturnedFail) {
TEST_F(BugreportTest, BugreportzReturnedFail) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    SetBugreportzVersion("1.1");
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL:D'OH!")), WithArg<4>(ReturnCallbackDone())));
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(
            DoAll(WithArg<4>(WriteOnStdout("FAIL:D'OH!\n")), WithArg<4>(ReturnCallbackDone())));


    CaptureStderr();
    CaptureStderr();
    const char* args[1024] = {"bugreport", "file.zip"};
    const char* args[1024] = {"bugreport", "file.zip"};
    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH"));
    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
}
}


// Tests 'adb bugreport file.zip' when the bugreport itself failed but response
// Tests 'adb bugreport file.zip' when the bugreport itself failed but response
// was sent in
// was sent in
// multiple buffer writes
// multiple buffer writes
TEST_F(BugreportTest, BugreportzReturnedFailSplitBuffer) {
TEST_F(BugreportTest, BugreportzReturnedFailSplitBuffer) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    SetBugreportzVersion("1.1");
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL")), WithArg<4>(WriteOnStdout(":D'OH!")),
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL")), WithArg<4>(WriteOnStdout(":D'OH!\n")),
                        WithArg<4>(ReturnCallbackDone())));
                        WithArg<4>(ReturnCallbackDone())));


    CaptureStderr();
    CaptureStderr();
    const char* args[1024] = {"bugreport", "file.zip"};
    const char* args[1024] = {"bugreport", "file.zip"};
    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH"));
    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
}
}


// Tests 'adb bugreport file.zip' when the bugreportz returned an unsupported
// Tests 'adb bugreport file.zip' when the bugreportz returned an unsupported
// response.
// response.
TEST_F(BugreportTest, BugreportzReturnedUnsupported) {
TEST_F(BugreportTest, BugreportzReturnedUnsupported) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    SetBugreportzVersion("1.1");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("bugreportz? What am I, a zombie?")),
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("bugreportz? What am I, a zombie?")),
                        WithArg<4>(ReturnCallbackDone())));
                        WithArg<4>(ReturnCallbackDone())));


@@ -192,9 +272,19 @@ TEST_F(BugreportTest, BugreportzReturnedUnsupported) {
    ASSERT_THAT(GetCapturedStderr(), HasSubstr("bugreportz? What am I, a zombie?"));
    ASSERT_THAT(GetCapturedStderr(), HasSubstr("bugreportz? What am I, a zombie?"));
}
}


// Tests 'adb bugreport file.zip' when the bugreportz command fails
// Tests 'adb bugreport file.zip' when the bugreportz -v command failed
TEST_F(BugreportTest, BugreportzVersionFailed) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
        .WillOnce(Return(666));

    const char* args[1024] = {"bugreport", "file.zip"};
    ASSERT_EQ(666, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}

// Tests 'adb bugreport file.zip' when the main bugreportz command failed
TEST_F(BugreportTest, BugreportzFailed) {
TEST_F(BugreportTest, BugreportzFailed) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    SetBugreportzVersion("1.1");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(Return(666));
        .WillOnce(Return(666));


    const char* args[1024] = {"bugreport", "file.zip"};
    const char* args[1024] = {"bugreport", "file.zip"};
@@ -203,7 +293,9 @@ TEST_F(BugreportTest, BugreportzFailed) {


// Tests 'adb bugreport file.zip' when the bugreport could not be pulled
// Tests 'adb bugreport file.zip' when the bugreport could not be pulled
TEST_F(BugreportTest, PullFails) {
TEST_F(BugreportTest, PullFails) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    SetBugreportzVersion("1.1");
    ExpectProgress(100, 100, true);
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
                        WithArg<4>(ReturnCallbackDone())));
                        WithArg<4>(ReturnCallbackDone())));
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),