1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "bugreport.h"
18
19#include <gmock/gmock.h>
20#include <gtest/gtest.h>
21
22#include <android-base/strings.h>
23#include <android-base/test_utils.h>
24
25#include "sysdeps.h"
26#include "adb_utils.h"
27
28using ::testing::_;
29using ::testing::Action;
30using ::testing::ActionInterface;
31using ::testing::DoAll;
32using ::testing::ElementsAre;
33using ::testing::HasSubstr;
34using ::testing::MakeAction;
35using ::testing::Return;
36using ::testing::StrEq;
37using ::testing::WithArg;
38using ::testing::internal::CaptureStderr;
39using ::testing::internal::CaptureStdout;
40using ::testing::internal::GetCapturedStderr;
41using ::testing::internal::GetCapturedStdout;
42
43// Empty function so tests don't need to be linked against file_sync_service.cpp, which requires
44// SELinux and its transitive dependencies...
45bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
46                  const char* name) {
47    ADD_FAILURE() << "do_sync_pull() should have been mocked";
48    return false;
49}
50
51// Empty functions so tests don't need to be linked against commandline.cpp
52DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK(nullptr, nullptr);
53
54int send_shell_command(const std::string& command, bool disable_shell_protocol,
55                       StandardStreamsCallbackInterface* callback) {
56    ADD_FAILURE() << "send_shell_command() should have been mocked";
57    return -42;
58}
59
60enum StreamType {
61    kStreamStdout,
62    kStreamStderr,
63};
64
65// gmock black magic to provide a WithArg<2>(WriteOnStdout(output)) matcher
66typedef void OnStandardStreamsCallbackFunction(StandardStreamsCallbackInterface*);
67
68class OnStandardStreamsCallbackAction : public ActionInterface<OnStandardStreamsCallbackFunction> {
69  public:
70    explicit OnStandardStreamsCallbackAction(StreamType type, const std::string& output)
71        : type_(type), output_(output) {
72    }
73    virtual Result Perform(const ArgumentTuple& args) {
74        if (type_ == kStreamStdout) {
75            ::std::tr1::get<0>(args)->OnStdout(output_.c_str(), output_.size());
76        }
77        if (type_ == kStreamStderr) {
78            ::std::tr1::get<0>(args)->OnStderr(output_.c_str(), output_.size());
79        }
80    }
81
82  private:
83    StreamType type_;
84    std::string output_;
85};
86
87// Matcher used to emulated StandardStreamsCallbackInterface.OnStdout(buffer,
88// length)
89Action<OnStandardStreamsCallbackFunction> WriteOnStdout(const std::string& output) {
90    return MakeAction(new OnStandardStreamsCallbackAction(kStreamStdout, output));
91}
92
93// Matcher used to emulated StandardStreamsCallbackInterface.OnStderr(buffer,
94// length)
95Action<OnStandardStreamsCallbackFunction> WriteOnStderr(const std::string& output) {
96    return MakeAction(new OnStandardStreamsCallbackAction(kStreamStderr, output));
97}
98
99typedef int CallbackDoneFunction(StandardStreamsCallbackInterface*);
100
101class CallbackDoneAction : public ActionInterface<CallbackDoneFunction> {
102  public:
103    explicit CallbackDoneAction(int status) : status_(status) {
104    }
105    virtual Result Perform(const ArgumentTuple& args) {
106        int status = ::std::tr1::get<0>(args)->Done(status_);
107        return status;
108    }
109
110  private:
111    int status_;
112};
113
114// Matcher used to emulated StandardStreamsCallbackInterface.Done(status)
115Action<CallbackDoneFunction> ReturnCallbackDone(int status = -1337) {
116    return MakeAction(new CallbackDoneAction(status));
117}
118
119class BugreportMock : public Bugreport {
120  public:
121    MOCK_METHOD3(SendShellCommand, int(const std::string& command, bool disable_shell_protocol,
122                                       StandardStreamsCallbackInterface* callback));
123    MOCK_METHOD4(DoSyncPull, bool(const std::vector<const char*>& srcs, const char* dst,
124                                  bool copy_attrs, const char* name));
125    MOCK_METHOD2(UpdateProgress, void(const std::string&, int));
126};
127
128class BugreportTest : public ::testing::Test {
129  public:
130    void SetUp() {
131        if (!getcwd(&cwd_)) {
132            ADD_FAILURE() << "getcwd failed: " << strerror(errno);
133            return;
134        }
135    }
136
137    void ExpectBugreportzVersion(const std::string& version) {
138        EXPECT_CALL(br_, SendShellCommand("bugreportz -v", false, _))
139            .WillOnce(DoAll(WithArg<2>(WriteOnStderr(version.c_str())),
140                            WithArg<2>(ReturnCallbackDone(0))));
141    }
142
143    void ExpectProgress(int progress_percentage, const std::string& file = "file.zip") {
144        EXPECT_CALL(br_, UpdateProgress(StrEq("generating " + file), progress_percentage));
145    }
146
147    BugreportMock br_;
148    std::string cwd_;  // TODO: make it static
149};
150
151// Tests when called with invalid number of arguments
152TEST_F(BugreportTest, InvalidNumberArgs) {
153    const char* args[] = {"bugreport", "to", "principal"};
154    ASSERT_EQ(1, br_.DoIt(3, args));
155}
156
157// Tests the 'adb bugreport' option when the device does not support 'bugreportz' - it falls back
158// to the flat-file format ('bugreport' binary on device)
159TEST_F(BugreportTest, NoArgumentsPreNDevice) {
160    // clang-format off
161    EXPECT_CALL(br_, SendShellCommand("bugreportz -v", false, _))
162        .WillOnce(DoAll(WithArg<2>(WriteOnStderr("")),
163                        // Write some bogus output on stdout to make sure it's ignored
164                        WithArg<2>(WriteOnStdout("Dude, where is my bugreportz?")),
165                        WithArg<2>(ReturnCallbackDone(0))));
166    // clang-format on
167    std::string bugreport = "Reported the bug was.";
168    CaptureStdout();
169    EXPECT_CALL(br_, SendShellCommand("bugreport", false, _))
170        .WillOnce(DoAll(WithArg<2>(WriteOnStdout(bugreport)), Return(0)));
171
172    const char* args[] = {"bugreport"};
173    ASSERT_EQ(0, br_.DoIt(1, args));
174    ASSERT_THAT(GetCapturedStdout(), StrEq(bugreport));
175}
176
177// Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.0 - it will
178// save the bugreport in the current directory with the name provided by the device.
179TEST_F(BugreportTest, NoArgumentsNDevice) {
180    ExpectBugreportzVersion("1.0");
181
182    std::string dest_file =
183        android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR);
184    EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _))
185        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip")),
186                        WithArg<2>(ReturnCallbackDone())));
187    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
188                                false, StrEq("pulling da_bugreport.zip")))
189        .WillOnce(Return(true));
190
191    const char* args[] = {"bugreport"};
192    ASSERT_EQ(0, br_.DoIt(1, args));
193}
194
195// Tests the 'adb bugreport' option when the device supports 'bugreportz' version 1.1 - it will
196// save the bugreport in the current directory with the name provided by the device.
197TEST_F(BugreportTest, NoArgumentsPostNDevice) {
198    ExpectBugreportzVersion("1.1");
199    std::string dest_file =
200        android::base::StringPrintf("%s%cda_bugreport.zip", cwd_.c_str(), OS_PATH_SEPARATOR);
201    ExpectProgress(50, "da_bugreport.zip");
202    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
203        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
204                        WithArg<2>(WriteOnStdout("PROGRESS:50/100\n")),
205                        WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip\n")),
206                        WithArg<2>(ReturnCallbackDone())));
207    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
208                                false, StrEq("pulling da_bugreport.zip")))
209        .WillOnce(Return(true));
210
211    const char* args[] = {"bugreport"};
212    ASSERT_EQ(0, br_.DoIt(1, args));
213}
214
215// Tests 'adb bugreport file.zip' when it succeeds and device does not support progress.
216TEST_F(BugreportTest, OkNDevice) {
217    ExpectBugreportzVersion("1.0");
218    EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _))
219        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")),
220                        WithArg<2>(ReturnCallbackDone())));
221    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
222                                false, StrEq("pulling file.zip")))
223        .WillOnce(Return(true));
224
225    const char* args[] = {"bugreport", "file.zip"};
226    ASSERT_EQ(0, br_.DoIt(2, args));
227}
228
229// Tests 'adb bugreport file.zip' when it succeeds but response was sent in
230// multiple buffer writers and without progress updates.
231TEST_F(BugreportTest, OkNDeviceSplitBuffer) {
232    ExpectBugreportzVersion("1.0");
233    EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _))
234        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device")),
235                        WithArg<2>(WriteOnStdout("/bugreport.zip")),
236                        WithArg<2>(ReturnCallbackDone())));
237    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
238                                false, StrEq("pulling file.zip")))
239        .WillOnce(Return(true));
240
241    const char* args[] = {"bugreport", "file.zip"};
242    ASSERT_EQ(0, br_.DoIt(2, args));
243}
244
245// Tests 'adb bugreport file.zip' when it succeeds and displays progress.
246TEST_F(BugreportTest, OkProgress) {
247    ExpectBugreportzVersion("1.1");
248    ExpectProgress(1);
249    ExpectProgress(10);
250    ExpectProgress(50);
251    ExpectProgress(99);
252    // clang-format off
253    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
254        // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit...
255        .WillOnce(DoAll(
256            // Name might change on OK, so make sure the right one is picked.
257            WithArg<2>(WriteOnStdout("BEGIN:/device/bugreport___NOT.zip\n")),
258            // Progress line in one write
259            WithArg<2>(WriteOnStdout("PROGRESS:1/100\n")),
260            // Add some bogus lines
261            WithArg<2>(WriteOnStdout("\nDUDE:SWEET\n\nBLA\n\nBLA\nBLA\n\n")),
262            // Multiple progress lines in one write
263            WithArg<2>(WriteOnStdout("PROGRESS:10/100\nPROGRESS:50/100\n")),
264            // Progress line in multiple writes
265            WithArg<2>(WriteOnStdout("PROG")),
266            WithArg<2>(WriteOnStdout("RESS:99")),
267            WithArg<2>(WriteOnStdout("/100\n")),
268            // Split last message as well, just in case
269            WithArg<2>(WriteOnStdout("OK:/device/bugreport")),
270            WithArg<2>(WriteOnStdout(".zip")),
271            WithArg<2>(ReturnCallbackDone())));
272    // clang-format on
273    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
274                                false, StrEq("pulling file.zip")))
275        .WillOnce(Return(true));
276
277    const char* args[] = {"bugreport", "file.zip"};
278    ASSERT_EQ(0, br_.DoIt(2, args));
279}
280
281// Tests 'adb bugreport file.zip' when it succeeds and displays progress, even if progress recedes.
282TEST_F(BugreportTest, OkProgressAlwaysForward) {
283    ExpectBugreportzVersion("1.1");
284    ExpectProgress(1);
285    ExpectProgress(50);
286    ExpectProgress(75);
287    // clang-format off
288    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
289        // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit...
290        .WillOnce(DoAll(
291            WithArg<2>(WriteOnStdout("BEGIN:/device/bugreport.zip\n")),
292            WithArg<2>(WriteOnStdout("PROGRESS:1/100\n")), // 1%
293            WithArg<2>(WriteOnStdout("PROGRESS:50/100\n")), // 50%
294            // 25% should be ignored becaused it receded.
295            WithArg<2>(WriteOnStdout("PROGRESS:25/100\n")), // 25%
296            WithArg<2>(WriteOnStdout("PROGRESS:75/100\n")), // 75%
297            // 75% should be ignored becaused it didn't change.
298            WithArg<2>(WriteOnStdout("PROGRESS:75/100\n")), // 75%
299            // Try a receeding percentage with a different max progress
300            WithArg<2>(WriteOnStdout("PROGRESS:700/1000\n")), // 70%
301            WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")),
302            WithArg<2>(ReturnCallbackDone())));
303    // clang-format on
304    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
305                                false, StrEq("pulling file.zip")))
306        .WillOnce(Return(true));
307
308    const char* args[] = {"bugreport", "file.zip"};
309    ASSERT_EQ(0, br_.DoIt(2, args));
310}
311
312// Tests 'adb bugreport file.zip' when it succeeds and displays the initial progress of 0%
313TEST_F(BugreportTest, OkProgressZeroPercentIsNotIgnored) {
314    ExpectBugreportzVersion("1.1");
315    ExpectProgress(0);
316    ExpectProgress(1);
317    // clang-format off
318    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
319        // NOTE: DoAll accepts at most 10 arguments, and we're almost reached that limit...
320        .WillOnce(DoAll(
321            WithArg<2>(WriteOnStdout("BEGIN:/device/bugreport.zip\n")),
322            WithArg<2>(WriteOnStdout("PROGRESS:1/100000\n")),
323            WithArg<2>(WriteOnStdout("PROGRESS:1/100\n")), // 1%
324            WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")),
325            WithArg<2>(ReturnCallbackDone())));
326    // clang-format on
327    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
328                                false, StrEq("pulling file.zip")))
329        .WillOnce(Return(true));
330
331    const char* args[] = {"bugreport", "file.zip"};
332    ASSERT_EQ(0, br_.DoIt(2, args));
333}
334
335// Tests 'adb bugreport dir' when it succeeds and destination is a directory.
336TEST_F(BugreportTest, OkDirectory) {
337    ExpectBugreportzVersion("1.1");
338    TemporaryDir td;
339    std::string dest_file =
340        android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR);
341
342    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
343        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
344                        WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip")),
345                        WithArg<2>(ReturnCallbackDone())));
346    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
347                                false, StrEq("pulling da_bugreport.zip")))
348        .WillOnce(Return(true));
349
350    const char* args[] = {"bugreport", td.path};
351    ASSERT_EQ(0, br_.DoIt(2, args));
352}
353
354// Tests 'adb bugreport file' when it succeeds
355TEST_F(BugreportTest, OkNoExtension) {
356    ExpectBugreportzVersion("1.1");
357    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
358        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip\n")),
359                        WithArg<2>(ReturnCallbackDone())));
360    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
361                                false, StrEq("pulling file.zip")))
362        .WillOnce(Return(true));
363
364    const char* args[] = {"bugreport", "file"};
365    ASSERT_EQ(0, br_.DoIt(2, args));
366}
367
368// Tests 'adb bugreport dir' when it succeeds and destination is a directory and device runs N.
369TEST_F(BugreportTest, OkNDeviceDirectory) {
370    ExpectBugreportzVersion("1.0");
371    TemporaryDir td;
372    std::string dest_file =
373        android::base::StringPrintf("%s%cda_bugreport.zip", td.path, OS_PATH_SEPARATOR);
374
375    EXPECT_CALL(br_, SendShellCommand("bugreportz", false, _))
376        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("BEGIN:/device/da_bugreport.zip\n")),
377                        WithArg<2>(WriteOnStdout("OK:/device/da_bugreport.zip")),
378                        WithArg<2>(ReturnCallbackDone())));
379    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/da_bugreport.zip")), StrEq(dest_file),
380                                false, StrEq("pulling da_bugreport.zip")))
381        .WillOnce(Return(true));
382
383    const char* args[] = {"bugreport", td.path};
384    ASSERT_EQ(0, br_.DoIt(2, args));
385}
386
387// Tests 'adb bugreport file.zip' when the bugreport itself failed
388TEST_F(BugreportTest, BugreportzReturnedFail) {
389    ExpectBugreportzVersion("1.1");
390    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
391        .WillOnce(
392            DoAll(WithArg<2>(WriteOnStdout("FAIL:D'OH!\n")), WithArg<2>(ReturnCallbackDone())));
393
394    CaptureStderr();
395    const char* args[] = {"bugreport", "file.zip"};
396    ASSERT_EQ(-1, br_.DoIt(2, args));
397    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
398}
399
400// Tests 'adb bugreport file.zip' when the bugreport itself failed but response
401// was sent in
402// multiple buffer writes
403TEST_F(BugreportTest, BugreportzReturnedFailSplitBuffer) {
404    ExpectBugreportzVersion("1.1");
405    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
406        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("FAIL")), WithArg<2>(WriteOnStdout(":D'OH!\n")),
407                        WithArg<2>(ReturnCallbackDone())));
408
409    CaptureStderr();
410    const char* args[] = {"bugreport", "file.zip"};
411    ASSERT_EQ(-1, br_.DoIt(2, args));
412    ASSERT_THAT(GetCapturedStderr(), HasSubstr("D'OH!"));
413}
414
415// Tests 'adb bugreport file.zip' when the bugreportz returned an unsupported
416// response.
417TEST_F(BugreportTest, BugreportzReturnedUnsupported) {
418    ExpectBugreportzVersion("1.1");
419    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
420        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("bugreportz? What am I, a zombie?")),
421                        WithArg<2>(ReturnCallbackDone())));
422
423    CaptureStderr();
424    const char* args[] = {"bugreport", "file.zip"};
425    ASSERT_EQ(-1, br_.DoIt(2, args));
426    ASSERT_THAT(GetCapturedStderr(), HasSubstr("bugreportz? What am I, a zombie?"));
427}
428
429// Tests 'adb bugreport file.zip' when the bugreportz -v command failed
430TEST_F(BugreportTest, BugreportzVersionFailed) {
431    EXPECT_CALL(br_, SendShellCommand("bugreportz -v", false, _)).WillOnce(Return(666));
432
433    const char* args[] = {"bugreport", "file.zip"};
434    ASSERT_EQ(666, br_.DoIt(2, args));
435}
436
437// Tests 'adb bugreport file.zip' when the bugreportz -v returns status 0 but with no output.
438TEST_F(BugreportTest, BugreportzVersionEmpty) {
439    ExpectBugreportzVersion("");
440
441    const char* args[] = {"bugreport", "file.zip"};
442    ASSERT_EQ(-1, br_.DoIt(2, args));
443}
444
445// Tests 'adb bugreport file.zip' when the main bugreportz command failed
446TEST_F(BugreportTest, BugreportzFailed) {
447    ExpectBugreportzVersion("1.1");
448    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _)).WillOnce(Return(666));
449
450    const char* args[] = {"bugreport", "file.zip"};
451    ASSERT_EQ(666, br_.DoIt(2, args));
452}
453
454// Tests 'adb bugreport file.zip' when the bugreport could not be pulled
455TEST_F(BugreportTest, PullFails) {
456    ExpectBugreportzVersion("1.1");
457    EXPECT_CALL(br_, SendShellCommand("bugreportz -p", false, _))
458        .WillOnce(DoAll(WithArg<2>(WriteOnStdout("OK:/device/bugreport.zip")),
459                        WithArg<2>(ReturnCallbackDone())));
460    EXPECT_CALL(br_, DoSyncPull(ElementsAre(StrEq("/device/bugreport.zip")), StrEq("file.zip"),
461                                false, HasSubstr("file.zip")))
462        .WillOnce(Return(false));
463
464    const char* args[] = {"bugreport", "file.zip"};
465    ASSERT_EQ(1, br_.DoIt(2, args));
466}
467