Split bugreport() into its own file and added unit tests.
am: 78e0963e4b

Change-Id: I0ae6ba38b62f714895e8d9abb5b36c5d8a82c6f2
diff --git a/adb/Android.mk b/adb/Android.mk
index 0babf1d..0681318 100644
--- a/adb/Android.mk
+++ b/adb/Android.mk
@@ -181,6 +181,9 @@
 LOCAL_CFLAGS_darwin := $(LIBADB_darwin_CFLAGS)
 LOCAL_SRC_FILES := \
     $(LIBADB_TEST_SRCS) \
+    adb_client.cpp \
+    bugreport.cpp \
+    bugreport_test.cpp \
     services.cpp \
     shell_service_protocol.cpp \
     shell_service_protocol_test.cpp \
@@ -196,6 +199,7 @@
     libcrypto_static \
     libcutils \
     libdiagnose_usb \
+    libgmock_host \
 
 # Set entrypoint to wmain from sysdeps_win32.cpp instead of main
 LOCAL_LDFLAGS_windows := -municode
@@ -224,6 +228,7 @@
 
 LOCAL_SRC_FILES := \
     adb_client.cpp \
+    bugreport.cpp \
     client/main.cpp \
     console.cpp \
     commandline.cpp \
diff --git a/adb/adb.h b/adb/adb.h
index 9227eb1..cd6b7bd 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -203,8 +203,6 @@
 int is_adb_interface(int vid, int pid, int usb_class, int usb_subclass, int usb_protocol);
 #endif
 
-int adb_commandline(int argc, const char **argv);
-
 ConnectionState connection_state(atransport *t);
 
 extern const char* adb_device_banner;
diff --git a/adb/adb_client.h b/adb/adb_client.h
index d5cd922..9f9eb1f 100644
--- a/adb/adb_client.h
+++ b/adb/adb_client.h
@@ -18,6 +18,7 @@
 #define _ADB_CLIENT_H_
 
 #include "adb.h"
+#include "sysdeps.h"
 #include "transport.h"
 
 #include <string>
diff --git a/adb/bugreport.cpp b/adb/bugreport.cpp
new file mode 100644
index 0000000..1af5843
--- /dev/null
+++ b/adb/bugreport.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include <android-base/strings.h>
+
+#include "bugreport.h"
+#include "commandline.h"
+#include "file_sync_service.h"
+
+static constexpr char BUGZ_OK_PREFIX[] = "OK:";
+static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
+
+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();
+
+    // Zipped bugreport option - will call 'bugreportz', which prints the location
+    // of the generated
+    // file, then pull it to the destination file provided by the user.
+    std::string dest_file = argv[1];
+    if (!android::base::EndsWith(argv[1], ".zip")) {
+        // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
+        dest_file += ".zip";
+    }
+    std::string output;
+
+    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");
+    int status = SendShellCommand(transport_type, serial, "bugreportz", false, &output, nullptr);
+    if (status != 0 || output.empty()) return status;
+    output = android::base::Trim(output);
+
+    if (android::base::StartsWith(output, BUGZ_OK_PREFIX)) {
+        const char* zip_file = &output[strlen(BUGZ_OK_PREFIX)];
+        std::vector<const char*> srcs{zip_file};
+        status = 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());
+        }
+        return status;
+    }
+    if (android::base::StartsWith(output, BUGZ_FAIL_PREFIX)) {
+        const char* error_message = &output[strlen(BUGZ_FAIL_PREFIX)];
+        fprintf(stderr, "Device failed to take a zipped bugreport: %s\n", error_message);
+        return -1;
+    }
+    fprintf(stderr,
+            "Unexpected string (%s) returned by bugreportz, "
+            "device probably does not support it\n",
+            output.c_str());
+    return -1;
+}
+
+int Bugreport::SendShellCommand(TransportType transport_type, const char* serial,
+                                const std::string& command, bool disable_shell_protocol,
+                                std::string* output, std::string* err) {
+    return send_shell_command(transport_type, serial, command, disable_shell_protocol, output, err);
+}
+
+bool Bugreport::DoSyncPull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                           const char* name) {
+    return do_sync_pull(srcs, dst, copy_attrs, name);
+}
diff --git a/adb/bugreport.h b/adb/bugreport.h
new file mode 100644
index 0000000..ff3a457
--- /dev/null
+++ b/adb/bugreport.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BUGREPORT_H
+#define BUGREPORT_H
+
+#include <vector>
+
+#include "adb.h"
+
+class Bugreport {
+  public:
+    Bugreport() {
+    }
+    int DoIt(TransportType transport_type, const char* serial, int argc, const char** argv);
+
+  protected:
+    // Functions below are abstractions of external functions so they can be
+    // mocked on tests.
+    virtual int SendShellCommand(TransportType transport_type, const char* serial,
+                                 const std::string& command, bool disable_shell_protocol,
+                                 std::string* output = nullptr, std::string* err = nullptr);
+
+    virtual bool DoSyncPull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                            const char* name);
+
+  private:
+    DISALLOW_COPY_AND_ASSIGN(Bugreport);
+};
+
+#endif  // BUGREPORT_H
diff --git a/adb/bugreport_test.cpp b/adb/bugreport_test.cpp
new file mode 100644
index 0000000..dd2ff37
--- /dev/null
+++ b/adb/bugreport_test.cpp
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "bugreport.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::HasSubstr;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::GetCapturedStderr;
+
+// Empty function so tests don't need to be linked against
+// file_sync_service.cpp, which requires
+// SELinux and its transitive dependencies...
+bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                  const char* name) {
+    ADD_FAILURE() << "do_sync_pull() should have been mocked";
+    return false;
+}
+
+// Implemented in commandline.cpp
+int usage() {
+    return -42;
+}
+
+// Implemented in commandline.cpp
+int send_shell_command(TransportType transport_type, const char* serial, const std::string& command,
+                       bool disable_shell_protocol, std::string* output, std::string* err) {
+    ADD_FAILURE() << "send_shell_command() should have been mocked";
+    return -42;
+}
+
+class BugreportMock : public Bugreport {
+  public:
+    MOCK_METHOD6(SendShellCommand,
+                 int(TransportType transport_type, const char* serial, const std::string& command,
+                     bool disable_shell_protocol, std::string* output, std::string* err));
+    MOCK_METHOD4(DoSyncPull, bool(const std::vector<const char*>& srcs, const char* dst,
+                                  bool copy_attrs, const char* name));
+};
+
+class BugreportTest : public ::testing::Test {
+  public:
+    BugreportMock br_;
+};
+
+// Tests when called with invalid number of argumnts
+TEST_F(BugreportTest, InvalidNumberArgs) {
+    const char* args[1024] = {"bugreport", "to", "principal"};
+    ASSERT_EQ(-42, br_.DoIt(kTransportLocal, "HannibalLecter", 3, args));
+}
+
+// Tests the legacy 'adb bugreport' option
+TEST_F(BugreportTest, FlatFileFormat) {
+    EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreport", false,
+                                      nullptr, nullptr))
+        .WillOnce(Return(0));
+
+    const char* args[1024] = {"bugreport"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 1, args));
+}
+
+// Tests 'adb bugreport file.zip' when it succeeds
+TEST_F(BugreportTest, Ok) {
+    EXPECT_CALL(
+        br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _, nullptr))
+        .WillOnce(DoAll(SetArgPointee<4>("OK:/device/bugreport.zip"), Return(0)));
+    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.zip"};
+    ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
+
+// Tests 'adb bugreport file' when it succeeds
+TEST_F(BugreportTest, OkNoExtension) {
+    EXPECT_CALL(
+        br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _, nullptr))
+        .WillOnce(DoAll(SetArgPointee<4>("OK:/device/bugreport.zip"), Return(0)));
+    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, _, nullptr))
+        .WillOnce(DoAll(SetArgPointee<4>("FAIL:D'OH!"), Return(0)));
+
+    CaptureStderr();
+    const char* args[1024] = {"bugreport", "file.zip"};
+    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+    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, _, nullptr))
+        .WillOnce(DoAll(SetArgPointee<4>("bugreportz? What am I, a zombie?"), Return(0)));
+
+    CaptureStderr();
+    const char* args[1024] = {"bugreport", "file.zip"};
+    ASSERT_EQ(-1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+    ASSERT_THAT(GetCapturedStderr(), HasSubstr("bugreportz? What am I, a zombie?"));
+}
+
+// Tests 'adb bugreport file.zip' when the bugreportz command fails
+TEST_F(BugreportTest, BugreportzFailed) {
+    EXPECT_CALL(
+        br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _, nullptr))
+        .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 bugreport could not be pulled
+TEST_F(BugreportTest, PullFails) {
+    EXPECT_CALL(
+        br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _, nullptr))
+        .WillOnce(DoAll(SetArgPointee<4>("OK:/device/bugreport.zip"), Return(0)));
+    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
+                                true, StrEq("file.zip")))
+        .WillOnce(Return(false));
+
+    const char* args[1024] = {"bugreport", "file.zip"};
+    ASSERT_EQ(1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
+}
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index d2ca44e..894ebd1 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -32,6 +32,7 @@
 #include "adb_auth.h"
 #include "adb_listeners.h"
 #include "adb_utils.h"
+#include "commandline.h"
 #include "transport.h"
 
 static std::string GetLogFilePath() {
diff --git a/adb/commandline.cpp b/adb/commandline.cpp
index 352283c..f699e5d 100644
--- a/adb/commandline.cpp
+++ b/adb/commandline.cpp
@@ -52,10 +52,11 @@
 #include "adb_client.h"
 #include "adb_io.h"
 #include "adb_utils.h"
+#include "bugreport.h"
+#include "commandline.h"
 #include "file_sync_service.h"
 #include "services.h"
 #include "shell_service.h"
-#include "transport.h"
 
 static int install_app(TransportType t, const char* serial, int argc, const char** argv);
 static int install_multiple_app(TransportType t, const char* serial, int argc, const char** argv);
@@ -66,9 +67,6 @@
 static auto& gProductOutPath = *new std::string();
 extern int gListenAll;
 
-static constexpr char BUGZ_OK_PREFIX[] = "OK:";
-static constexpr char BUGZ_FAIL_PREFIX[] = "FAIL:";
-
 static std::string product_file(const char *extra) {
     if (gProductOutPath.empty()) {
         fprintf(stderr, "adb: Product directory not specified; "
@@ -254,7 +252,7 @@
         );
 }
 
-static int usage() {
+int usage() {
     help();
     return 1;
 }
@@ -1119,13 +1117,8 @@
     return true;
 }
 
-// Connects to the device "shell" service with |command| and prints the
-// resulting output.
-static int send_shell_command(TransportType transport_type, const char* serial,
-                              const std::string& command,
-                              bool disable_shell_protocol,
-                              std::string* output=nullptr,
-                              std::string* err=nullptr) {
+int send_shell_command(TransportType transport_type, const char* serial, const std::string& command,
+                       bool disable_shell_protocol, std::string* output, std::string* err) {
     int fd;
     bool use_shell_protocol = false;
 
@@ -1169,45 +1162,6 @@
     return exit_code;
 }
 
-static int bugreport(TransportType transport_type, const char* serial, int argc,
-                     const char** argv) {
-    if (argc == 1) return send_shell_command(transport_type, serial, "bugreport", false);
-    if (argc != 2) return usage();
-
-    // Zipped bugreport option - will call 'bugreportz', which prints the location of the generated
-    // file, then pull it to the destination file provided by the user.
-    std::string dest_file = argv[1];
-    if (!android::base::EndsWith(argv[1], ".zip")) {
-        // TODO: use a case-insensitive comparison (like EndsWithIgnoreCase
-        dest_file += ".zip";
-    }
-    std::string output;
-
-    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");
-    int status = send_shell_command(transport_type, serial, "bugreportz", false, &output, nullptr);
-    if (status != 0 || output.empty()) return status;
-    output = android::base::Trim(output);
-
-    if (android::base::StartsWith(output, BUGZ_OK_PREFIX)) {
-        const char* zip_file = &output[strlen(BUGZ_OK_PREFIX)];
-        std::vector<const char*> srcs{zip_file};
-        status = do_sync_pull(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());
-        }
-        return status;
-    }
-    if (android::base::StartsWith(output, BUGZ_FAIL_PREFIX)) {
-        const char* error_message = &output[strlen(BUGZ_FAIL_PREFIX)];
-        fprintf(stderr, "Device failed to take a zipped bugreport: %s\n", error_message);
-        return -1;
-    }
-    fprintf(stderr, "Unexpected string (%s) returned by bugreportz, "
-            "device probably does not support -z option\n", output.c_str());
-    return -1;
-}
-
 static int logcat(TransportType transport, const char* serial, int argc, const char** argv) {
     char* log_tags = getenv("ANDROID_LOG_TAGS");
     std::string quoted = escape_arg(log_tags == nullptr ? "" : log_tags);
@@ -1778,7 +1732,8 @@
     } else if (!strcmp(argv[0], "root") || !strcmp(argv[0], "unroot")) {
         return adb_root(argv[0]) ? 0 : 1;
     } else if (!strcmp(argv[0], "bugreport")) {
-        return bugreport(transport_type, serial, argc, argv);
+        Bugreport bugreport;
+        return bugreport.DoIt(transport_type, serial, argc, argv);
     } else if (!strcmp(argv[0], "forward") || !strcmp(argv[0], "reverse")) {
         bool reverse = !strcmp(argv[0], "reverse");
         ++argv;
diff --git a/adb/commandline.h b/adb/commandline.h
new file mode 100644
index 0000000..39764b4
--- /dev/null
+++ b/adb/commandline.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COMMANDLINE_H
+#define COMMANDLINE_H
+
+#include "adb.h"
+
+int adb_commandline(int argc, const char** argv);
+int usage();
+
+// Connects to the device "shell" service with |command| and prints the
+// resulting output.
+int send_shell_command(TransportType transport_type, const char* serial, const std::string& command,
+                       bool disable_shell_protocol, std::string* output = nullptr,
+                       std::string* err = nullptr);
+
+#endif  // COMMANDLINE_H