diff --git a/adb/Android.mk b/adb/Android.mk index f1d3bee95..27b8a0006 100644 --- a/adb/Android.mk +++ b/adb/Android.mk @@ -180,6 +180,9 @@ LOCAL_CFLAGS_linux := $(LIBADB_linux_CFLAGS) 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 \ @@ -194,6 +197,7 @@ LOCAL_STATIC_LIBRARIES := \ libcrypto_static \ libcutils \ libdiagnose_usb \ + libgmock_host \ # Set entrypoint to wmain from sysdeps_win32.cpp instead of main LOCAL_LDFLAGS_windows := -municode @@ -240,6 +244,7 @@ LOCAL_REQUIRED_MODULES_windows := AdbWinApi AdbWinUsbApi 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 ea208004d..971b8da41 100644 --- a/adb/adb.h +++ b/adb/adb.h @@ -226,8 +226,6 @@ void usb_kick(usb_handle *h); 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 d5cd9220a..9f9eb1f5e 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 diff --git a/adb/bugreport.cpp b/adb/bugreport.cpp new file mode 100644 index 000000000..1af5843c6 --- /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 + +#include + +#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 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& 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 000000000..ff3a45726 --- /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 + +#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& 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 000000000..dd2ff37fc --- /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 +#include + +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& 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& 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 b0722ef53..ba4737f8d 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 af2c81f9b..6cb9ae2e8 100644 --- a/adb/commandline.cpp +++ b/adb/commandline.cpp @@ -51,10 +51,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); @@ -65,9 +66,6 @@ static int uninstall_app_legacy(TransportType t, const char* serial, int argc, c 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; " @@ -253,7 +251,7 @@ static void help() { ); } -static int usage() { +int usage() { help(); return 1; } @@ -1131,13 +1129,8 @@ static bool adb_root(const char* command) { return wait_for_device("wait-for-any", type, serial); } -// 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; @@ -1181,45 +1174,6 @@ static int send_shell_command(TransportType transport_type, const char* serial, 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 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); @@ -1775,7 +1729,8 @@ int adb_commandline(int argc, const char **argv) { } 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 000000000..39764b452 --- /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