android_system_core/adb/bugreport_test.cpp
Felipe Leme 899e6321aa Always print the initial bugreport progress message.
Otherwise, it might take seconds to display the initial percentage,
and the user might thing something is wrong and CTRL-C the process.

Fixes: 62003852
Test: added BugreportTest.OkProgressZeroPercentIsNotIgnored
Test: m -j32 adb_test && ./out/host/linux-x86/nativetest64/adb_test/adb_test --gtest_filter=BugreportTest.*

Change-Id: I8e7dbb28666c825c1408d5d204f813064fc3048f
(cherry picked from commit e5d665b46f)
2017-05-23 19:01:18 -07:00

470 lines
21 KiB
C++

/*
* 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(TransportType transport_type, const char* serial, 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<4>(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_METHOD5(SendShellCommand,
int(TransportType transport_type, const char* serial, 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(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
.WillOnce(DoAll(WithArg<4>(WriteOnStderr(version.c_str())),
WithArg<4>(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(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
.WillOnce(DoAll(WithArg<4>(WriteOnStderr("")),
// Write some bogus output on stdout to make sure it's ignored
WithArg<4>(WriteOnStdout("Dude, where is my bugreportz?")),
WithArg<4>(ReturnCallbackDone(0))));
// clang-format on
std::string bugreport = "Reported the bug was.";
CaptureStdout();
EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreport", false, _))
.WillOnce(DoAll(WithArg<4>(WriteOnStdout(bugreport)), Return(0)));
const char* args[] = {"bugreport"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
.WillOnce(DoAll(WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
WithArg<4>(ReturnCallbackDone())));
EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
true, StrEq("pulling da_bugreport.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
.WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
WithArg<4>(WriteOnStdout("PROGRESS:50/100\n")),
WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip\n")),
WithArg<4>(ReturnCallbackDone())));
EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
true, StrEq("pulling da_bugreport.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz", 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"),
true, StrEq("pulling file.zip")))
.WillOnce(Return(true));
const char* args[] = {"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 and without progress updates.
TEST_F(BugreportTest, OkNDeviceSplitBuffer) {
ExpectBugreportzVersion("1.0");
EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
.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("pulling file.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport", "file.zip"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "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<4>(WriteOnStdout("BEGIN:/device/bugreport___NOT.zip\n")),
// Progress line in one write
WithArg<4>(WriteOnStdout("PROGRESS:1/100\n")),
// Add some bogus lines
WithArg<4>(WriteOnStdout("\nDUDE:SWEET\n\nBLA\n\nBLA\nBLA\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())));
// clang-format on
EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
true, StrEq("pulling file.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport", "file.zip"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
// NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit...
.WillOnce(DoAll(
WithArg<4>(WriteOnStdout("BEGIN:/device/bugreport.zip\n")),
WithArg<4>(WriteOnStdout("PROGRESS:1/100\n")), // 1%
WithArg<4>(WriteOnStdout("PROGRESS:50/100\n")), // 50%
// 25% should be ignored becaused it receded.
WithArg<4>(WriteOnStdout("PROGRESS:25/100\n")), // 25%
WithArg<4>(WriteOnStdout("PROGRESS:75/100\n")), // 75%
// 75% should be ignored becaused it didn't change.
WithArg<4>(WriteOnStdout("PROGRESS:75/100\n")), // 75%
// Try a receeding percentage with a different max progress
WithArg<4>(WriteOnStdout("PROGRESS:700/1000\n")), // 70%
WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
WithArg<4>(ReturnCallbackDone())));
// clang-format on
EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
true, StrEq("pulling file.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport", "file.zip"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
// NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit...
.WillOnce(DoAll(
WithArg<4>(WriteOnStdout("BEGIN:/device/bugreport.zip\n")),
WithArg<4>(WriteOnStdout("PROGRESS:1/100000\n")),
WithArg<4>(WriteOnStdout("PROGRESS:1/100\n")), // 1%
WithArg<4>(WriteOnStdout("OK:/device/bugreport.zip")),
WithArg<4>(ReturnCallbackDone())));
// clang-format on
EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
true, StrEq("pulling file.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport", "file.zip"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
.WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
WithArg<4>(ReturnCallbackDone())));
EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
true, StrEq("pulling da_bugreport.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport", td.path};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}
// Tests 'adb bugreport file' when it succeeds
TEST_F(BugreportTest, OkNoExtension) {
ExpectBugreportzVersion("1.1");
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("pulling file.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport", "file"};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", "bugreportz", false, _))
.WillOnce(DoAll(WithArg<4>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
WithArg<4>(WriteOnStdout("OK:/device/da_bugreport.zip")),
WithArg<4>(ReturnCallbackDone())));
EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
true, StrEq("pulling da_bugreport.zip")))
.WillOnce(Return(true));
const char* args[] = {"bugreport", td.path};
ASSERT_EQ(0, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}
// Tests 'adb bugreport file.zip' when the bugreport itself failed
TEST_F(BugreportTest, BugreportzReturnedFail) {
ExpectBugreportzVersion("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[] = {"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 bugreport itself failed but response
// was sent in
// multiple buffer writes
TEST_F(BugreportTest, BugreportzReturnedFailSplitBuffer) {
ExpectBugreportzVersion("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[] = {"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) {
ExpectBugreportzVersion("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())));
CaptureStderr();
const char* args[] = {"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 -v command failed
TEST_F(BugreportTest, BugreportzVersionFailed) {
EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -v", false, _))
.WillOnce(Return(666));
const char* args[] = {"bugreport", "file.zip"};
ASSERT_EQ(666, br_.DoIt(kTransportLocal, "HannibalLecter", 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(kTransportLocal, "HannibalLecter", 2, args));
}
// Tests 'adb bugreport file.zip' when the main bugreportz command failed
TEST_F(BugreportTest, BugreportzFailed) {
ExpectBugreportzVersion("1.1");
EXPECT_CALL(br_, SendShellCommand(kTransportLocal, "HannibalLecter", "bugreportz -p", false, _))
.WillOnce(Return(666));
const char* args[] = {"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) {
ExpectBugreportzVersion("1.1");
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"),
true, HasSubstr("file.zip")))
.WillOnce(Return(false));
const char* args[] = {"bugreport", "file.zip"};
ASSERT_EQ(1, br_.DoIt(kTransportLocal, "HannibalLecter", 2, args));
}