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 Diff line number Diff line
@@ -187,6 +187,7 @@ LOCAL_SRC_FILES := \
    adb_client.cpp \
    bugreport.cpp \
    bugreport_test.cpp \
    line_printer.cpp \
    services.cpp \
    shell_service_protocol.cpp \
    shell_service_protocol_test.cpp \
+102 −27
Original line number Diff line number Diff line
@@ -23,55 +23,92 @@

static constexpr char BUGZ_OK_PREFIX[] = "OK:";
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.
class BugreportStandardStreamsCallback : public StandardStreamsCallbackInterface {
  public:
    BugreportStandardStreamsCallback(const std::string& dest_file, Bugreport* br)
        : br_(br), dest_file_(dest_file), stdout_str_() {
    BugreportStandardStreamsCallback(const std::string& dest_file, bool show_progress, Bugreport* br)
        : br_(br), dest_file_(dest_file), show_progress_(show_progress), status_(-1), line_() {
    }

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

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

    int Done(int unused_) {
        int status = -1;
        std::string output = android::base::Trim(stdout_str_);
        if (android::base::StartsWith(output, BUGZ_OK_PREFIX)) {
            const char* zip_file = &output[strlen(BUGZ_OK_PREFIX)];
        // Process remaining line, if any...
        ProcessLine(line_);
        // ..then return.
        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};
            status = br_->DoSyncPull(srcs, dest_file_.c_str(), true, dest_file_.c_str()) ? 0 : 1;
            if (status != 0) {
            status_ = br_->DoSyncPull(srcs, dest_file_.c_str(), true, dest_file_.c_str()) ? 0 : 1;
            if (status_ != 0) {
                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)) {
            const char* error_message = &output[strlen(BUGZ_FAIL_PREFIX)];
        } else if (android::base::StartsWith(line, 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);
            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 {
            fprintf(stderr,
                    "Unexpected string (%s) returned by bugreportz, "
                    "device probably does not support it\n",
                    output.c_str());
                    "WARNING: unexpected line (%s) returned by bugreportz, "
                    "device probably does not support zipped bugreports.\n"
                    "Try 'adb bugreport' instead.",
                    line.c_str());
        }

        return status;
    }

  private:
    Bugreport* br_;
    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);
};

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

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 != 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
        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,
                "Bugreport is in progress and it could take minutes to complete.\n"
            "Please be patient and do not cancel or disconnect your device until "
            "it completes.\n");
    BugreportStandardStreamsCallback bugz_callback(dest_file, this);
    return SendShellCommand(transport_type, serial, "bugreportz", false, &bugz_callback);
                "Please be patient and do not cancel or disconnect your device "
                "until it completes."
                "\n");
        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,
+5 −1
Original line number Diff line number Diff line
@@ -21,12 +21,13 @@

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

class Bugreport {
    friend class BugreportStandardStreamsCallback;

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

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

  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);
};

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

enum StreamType {
    kStreamStdout,
    kStreamStderr,
};

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

class OnStandardStreamsCallbackAction : public ActionInterface<OnStandardStreamsCallbackFunction> {
  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) {
        if (type_ == kStreamStdout) {
            ::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:
    StreamType type_;
    std::string output_;
};

// Matcher used to emulated StandardStreamsCallbackInterface.OnStdout(buffer,
// length)
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*);

class CallbackDoneAction : public ActionInterface<CallbackDoneFunction> {
  public:
    explicit CallbackDoneAction(int status) : status_(status) {
    }
    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;
    }

  private:
    int status_;
};

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

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

class BugreportTest : public ::testing::Test {
  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_;
};

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

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

// Tests 'adb bugreport file' when it succeeds
TEST_F(BugreportTest, OkNoExtension) {
// Tests 'adb bugreport file.zip' when it succeeds but response was sent in
// multiple buffer writers and without progress updates.
TEST_F(BugreportTest, OkLegacySplitBuffer) {
    SetBugreportzVersion("1.0");
    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())));
    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"};
    const char* args[1024] = {"bugreport", "file.zip"};
    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}

// Tests 'adb bugreport file.zip' when it succeeds but response was sent in
// multiple buffer writers.
TEST_F(BugreportTest, OkSplitBuffer) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device")),
                        WithArg<4>(WriteOnStdout("/bugreport.zip")),
// Tests 'adb bugreport file.zip' when it succeeds and displays progress.
TEST_F(BugreportTest, Ok) {
    SetBugreportzVersion("1.1");
    ExpectProgress(1, 100);
    ExpectProgress(10, 100);
    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())));
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
                                true, StrEq("file.zip")))
@@ -154,35 +215,54 @@ TEST_F(BugreportTest, OkSplitBuffer) {
    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
TEST_F(BugreportTest, BugreportzReturnedFail) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL:D'OH!")), WithArg<4>(ReturnCallbackDone())));
    SetBugreportzVersion("1.1");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(
            DoAll(WithArg<4>(WriteOnStdout("FAIL:D'OH!\n")), WithArg<4>(ReturnCallbackDone())));

    CaptureStderr();
    const char* args[1024] = {"bugreport", "file.zip"};
    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
// was sent in
// multiple buffer writes
TEST_F(BugreportTest, BugreportzReturnedFailSplitBuffer) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL")), WithArg<4>(WriteOnStdout(":D'OH!")),
    SetBugreportzVersion("1.1");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(DoAll(WithArg<4>(WriteOnStdout("FAIL")), WithArg<4>(WriteOnStdout(":D'OH!\n")),
                        WithArg<4>(ReturnCallbackDone())));

    CaptureStderr();
    const char* args[1024] = {"bugreport", "file.zip"};
    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
// response.
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?")),
                        WithArg<4>(ReturnCallbackDone())));

@@ -192,9 +272,19 @@ TEST_F(BugreportTest, BugreportzReturnedUnsupported) {
    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) {
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
    SetBugreportzVersion("1.1");
    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
        .WillOnce(Return(666));

    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
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")),
                        WithArg<4>(ReturnCallbackDone())));
    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),