| /* | |
| * 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> | |
| #include <android-base/strings.h> | |
| #include <android-base/test_utils.h> | |
| #include "sysdeps.h" | |
| #include "adb_utils.h" | |
| using ::testing::_; | |
| using ::testing::Action; | |
| using ::testing::ActionInterface; | |
| using ::testing::DoAll; | |
| using ::testing::ElementsAre; | |
| using ::testing::HasSubstr; | |
| using ::testing::MakeAction; | |
| using ::testing::Return; | |
| using ::testing::StrEq; | |
| using ::testing::WithArg; | |
| using ::testing::internal::CaptureStderr; | |
| using ::testing::internal::CaptureStdout; | |
| using ::testing::internal::GetCapturedStderr; | |
| using ::testing::internal::GetCapturedStdout; | |
| // 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; | |
| } | |
| // Empty functions so tests don't need to be linked against commandline.cpp | |
| DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK(nullptr, nullptr); | |
| int send_shell_command(const std::string& command, bool disable_shell_protocol, | |
| StandardStreamsCallbackInterface* callback) { | |
| ADD_FAILURE() << "send_shell_command() should have been mocked"; | |
| return -42; | |
| } | |
| enum StreamType { | |
| kStreamStdout, | |
| kStreamStderr, | |
| }; | |
| // gmock black magic to provide a WithArg<2>(WriteOnStdout(output)) matcher | |
| typedef void OnStandardStreamsCallbackFunction(StandardStreamsCallbackInterface*); | |
| class OnStandardStreamsCallbackAction : public ActionInterface<OnStandardStreamsCallbackFunction> { | |
| public: | |
| 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(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(status_); | |
| return status; | |
| } | |
| private: | |
| int status_; | |
| }; | |
| // Matcher used to emulated StandardStreamsCallbackInterface.Done(status) | |
| Action<CallbackDoneFunction> ReturnCallbackDone(int status = -1337) { | |
| return MakeAction(new CallbackDoneAction(status)); | |
| } | |
| class BugreportMock : public Bugreport { | |
| public: | |
| MOCK_METHOD3(SendShellCommand, int(const std::string& command, 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_METHOD2(UpdateProgress, void(const std::string&, int)); | |
| }; | |
| class BugreportTest : public ::testing::Test { | |
| public: | |
| void SetUp() { | |
| if (!getcwd(&cwd_)) { | |
| ADD_FAILURE() << "getcwd failed: " << strerror(errno); | |
| return; | |
| } | |
| } | |
| void ExpectBugreportzVersion(const std::string& version) { | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -v", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStderr(version)), | |
| WithArg<2>(ReturnCallbackDone(0)))); | |
| } | |
| void ExpectProgress(int progress_percentage, const std::string& file = "file.zip") { | |
| EXPECT_CALL(br_, UpdateProgress(StrEq("generating " + file), progress_percentage)); | |
| } | |
| BugreportMock br_; | |
| std::string cwd_; // TODO: make it static | |
| }; | |
| // Tests when called with invalid number of arguments | |
| TEST_F(BugreportTest, InvalidNumberArgs) { | |
| const char* args[] = {"bugreport", "to", "principal"}; | |
| ASSERT_EQ(1, br_.DoIt(3, args)); | |
| } | |
| // Tests the 'adb bugreport' option when the device does not support 'bugreportz' - it falls back | |
| // to the flat-file format ('bugreport' binary on device) | |
| TEST_F(BugreportTest, NoArgumentsPreNDevice) { | |
| // clang-format off | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -v", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStderr("")), | |
| // Write some bogus output on stdout to make sure it's ignored | |
| WithArg<2>(WriteOnStdout("Dude, where is my bugreportz?")), | |
| WithArg<2>(ReturnCallbackDone(0)))); | |
| // clang-format on | |
| std::string bugreport = "Reported the bug was."; | |
| CaptureStdout(); | |
| EXPECT_CALL(br_, SendShellCommand("bugreport", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout(bugreport)), Return(0))); | |
| const char* args[] = {"bugreport"}; | |
| ASSERT_EQ(0, br_.DoIt(1, args)); | |
| ASSERT_THAT(GetCapturedStdout(), StrEq(bugreport)); | |
| } | |
| // Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.0 - it will | |
| // save the bugreport in the current directory with the name provided by the device. | |
| TEST_F(BugreportTest, NoArgumentsNDevice) { | |
| ExpectBugreportzVersion("1.0"); | |
| std::string dest_file = | |
| android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file), | |
| false, StrEq("pulling da_bugreport.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport"}; | |
| ASSERT_EQ(0, br_.DoIt(1, args)); | |
| } | |
| // Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.1 - it will | |
| // save the bugreport in the current directory with the name provided by the device. | |
| TEST_F(BugreportTest, NoArgumentsPostNDevice) { | |
| ExpectBugreportzVersion("1.1"); | |
| std::string dest_file = | |
| android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR); | |
| ExpectProgress(50, "da_bugreport.zip"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")), | |
| WithArg<2>(WriteOnStdout("PROGRESS:50/100\n")), | |
| WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip\n")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file), | |
| false, StrEq("pulling da_bugreport.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport"}; | |
| ASSERT_EQ(0, br_.DoIt(1, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when it succeeds and device does not support progress. | |
| TEST_F(BugreportTest, OkNDevice) { | |
| ExpectBugreportzVersion("1.0"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"), | |
| false, StrEq("pulling file.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when it succeeds but response was sent in | |
| // multiple buffer writers and without progress updates. | |
| TEST_F(BugreportTest, OkNDeviceSplitBuffer) { | |
| ExpectBugreportzVersion("1.0"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device")), | |
| WithArg<2>(WriteOnStdout("/bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"), | |
| false, StrEq("pulling file.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when it succeeds and displays progress. | |
| TEST_F(BugreportTest, OkProgress) { | |
| ExpectBugreportzVersion("1.1"); | |
| ExpectProgress(1); | |
| ExpectProgress(10); | |
| ExpectProgress(50); | |
| ExpectProgress(99); | |
| // clang-format off | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit... | |
| .WillOnce(DoAll( | |
| // Name might change on OK, so make sure the right one is picked. | |
| WithArg<2>(WriteOnStdout("BEGIN:/device/bugreport___NOT.zip\n")), | |
| // Progress line in one write | |
| WithArg<2>(WriteOnStdout("PROGRESS:1/100\n")), | |
| // Add some bogus lines | |
| WithArg<2>(WriteOnStdout("\nDUDE:SWEET\n\nBLA\n\nBLA\nBLA\n\n")), | |
| // Multiple progress lines in one write | |
| WithArg<2>(WriteOnStdout("PROGRESS:10/100\nPROGRESS:50/100\n")), | |
| // Progress line in multiple writes | |
| WithArg<2>(WriteOnStdout("PROG")), | |
| WithArg<2>(WriteOnStdout("RESS:99")), | |
| WithArg<2>(WriteOnStdout("/100\n")), | |
| // Split last message as well, just in case | |
| WithArg<2>(WriteOnStdout("OK:/device/bugreport")), | |
| WithArg<2>(WriteOnStdout(".zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| // clang-format on | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"), | |
| false, StrEq("pulling file.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when it succeeds and displays progress, even if progress recedes. | |
| TEST_F(BugreportTest, OkProgressAlwaysForward) { | |
| ExpectBugreportzVersion("1.1"); | |
| ExpectProgress(1); | |
| ExpectProgress(50); | |
| ExpectProgress(75); | |
| // clang-format off | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit... | |
| .WillOnce(DoAll( | |
| WithArg<2>(WriteOnStdout("BEGIN:/device/bugreport.zip\n")), | |
| WithArg<2>(WriteOnStdout("PROGRESS:1/100\n")), // 1% | |
| WithArg<2>(WriteOnStdout("PROGRESS:50/100\n")), // 50% | |
| // 25% should be ignored becaused it receded. | |
| WithArg<2>(WriteOnStdout("PROGRESS:25/100\n")), // 25% | |
| WithArg<2>(WriteOnStdout("PROGRESS:75/100\n")), // 75% | |
| // 75% should be ignored becaused it didn't change. | |
| WithArg<2>(WriteOnStdout("PROGRESS:75/100\n")), // 75% | |
| // Try a receeding percentage with a different max progress | |
| WithArg<2>(WriteOnStdout("PROGRESS:700/1000\n")), // 70% | |
| WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| // clang-format on | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"), | |
| false, StrEq("pulling file.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when it succeeds and displays the initial progress of 0% | |
| TEST_F(BugreportTest, OkProgressZeroPercentIsNotIgnored) { | |
| ExpectBugreportzVersion("1.1"); | |
| ExpectProgress(0); | |
| ExpectProgress(1); | |
| // clang-format off | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit... | |
| .WillOnce(DoAll( | |
| WithArg<2>(WriteOnStdout("BEGIN:/device/bugreport.zip\n")), | |
| WithArg<2>(WriteOnStdout("PROGRESS:1/100000\n")), | |
| WithArg<2>(WriteOnStdout("PROGRESS:1/100\n")), // 1% | |
| WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| // clang-format on | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"), | |
| false, StrEq("pulling file.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport dir' when it succeeds and destination is a directory. | |
| TEST_F(BugreportTest, OkDirectory) { | |
| ExpectBugreportzVersion("1.1"); | |
| TemporaryDir td; | |
| std::string dest_file = | |
| android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")), | |
| WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file), | |
| false, StrEq("pulling da_bugreport.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", td.path}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file' when it succeeds | |
| TEST_F(BugreportTest, OkNoExtension) { | |
| ExpectBugreportzVersion("1.1"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip\n")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"), | |
| false, StrEq("pulling file.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", "file"}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport dir' when it succeeds and destination is a directory and device runs N. | |
| TEST_F(BugreportTest, OkNDeviceDirectory) { | |
| ExpectBugreportzVersion("1.0"); | |
| TemporaryDir td; | |
| std::string dest_file = | |
| android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")), | |
| WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file), | |
| false, StrEq("pulling da_bugreport.zip"))) | |
| .WillOnce(Return(true)); | |
| const char* args[] = {"bugreport", td.path}; | |
| ASSERT_EQ(0, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when the bugreport itself failed | |
| TEST_F(BugreportTest, BugreportzReturnedFail) { | |
| ExpectBugreportzVersion("1.1"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| .WillOnce( | |
| DoAll(WithArg<2>(WriteOnStdout("FAIL:D'OH!\n")), WithArg<2>(ReturnCallbackDone()))); | |
| CaptureStderr(); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(-1, br_.DoIt(2, args)); | |
| 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) { | |
| ExpectBugreportzVersion("1.1"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("FAIL")), WithArg<2>(WriteOnStdout(":D'OH!\n")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| CaptureStderr(); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(-1, br_.DoIt(2, args)); | |
| ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!")); | |
| } | |
| // Tests 'adb bugreport file.zip' when the bugreportz returned an unsupported | |
| // response. | |
| TEST_F(BugreportTest, BugreportzReturnedUnsupported) { | |
| ExpectBugreportzVersion("1.1"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("bugreportz? What am I, a zombie?")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| CaptureStderr(); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(-1, br_.DoIt(2, args)); | |
| ASSERT_THAT(GetCapturedStderr(), HasSubstr("bugreportz? What am I, a zombie?")); | |
| } | |
| // Tests 'adb bugreport file.zip' when the bugreportz -v command failed | |
| TEST_F(BugreportTest, BugreportzVersionFailed) { | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -v", false, _)).WillOnce(Return(666)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(666, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when the bugreportz -v returns status 0 but with no output. | |
| TEST_F(BugreportTest, BugreportzVersionEmpty) { | |
| ExpectBugreportzVersion(""); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(-1, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when the main bugreportz command failed | |
| TEST_F(BugreportTest, BugreportzFailed) { | |
| ExpectBugreportzVersion("1.1"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)).WillOnce(Return(666)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(666, br_.DoIt(2, args)); | |
| } | |
| // Tests 'adb bugreport file.zip' when the bugreport could not be pulled | |
| TEST_F(BugreportTest, PullFails) { | |
| ExpectBugreportzVersion("1.1"); | |
| EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)) | |
| .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")), | |
| WithArg<2>(ReturnCallbackDone()))); | |
| EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"), | |
| false, HasSubstr("file.zip"))) | |
| .WillOnce(Return(false)); | |
| const char* args[] = {"bugreport", "file.zip"}; | |
| ASSERT_EQ(1, br_.DoIt(2, args)); | |
| } |