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 
28 using ::testing::_;
29 using ::testing::Action;
30 using ::testing::ActionInterface;
31 using ::testing::DoAll;
32 using ::testing::ElementsAre;
33 using ::testing::HasSubstr;
34 using ::testing::MakeAction;
35 using ::testing::Return;
36 using ::testing::StrEq;
37 using ::testing::WithArg;
38 using ::testing::internal::CaptureStderr;
39 using ::testing::internal::CaptureStdout;
40 using ::testing::internal::GetCapturedStderr;
41 using ::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...
45 bool 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
52 DefaultStandardStreamsCallback DEFAULT_STANDARD_STREAMS_CALLBACK(nullptr, nullptr);
53 
54 int 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 
60 enum StreamType {
61     kStreamStdout,
62     kStreamStderr,
63 };
64 
65 // gmock black magic to provide a WithArg<2>(WriteOnStdout(output)) matcher
66 typedef void OnStandardStreamsCallbackFunction(StandardStreamsCallbackInterface*);
67 
68 class 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)
89 Action<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)
95 Action<OnStandardStreamsCallbackFunction> WriteOnStderr(const std::string& output) {
96     return MakeAction(new OnStandardStreamsCallbackAction(kStreamStderr, output));
97 }
98 
99 typedef int CallbackDoneFunction(StandardStreamsCallbackInterface*);
100 
101 class 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)
115 Action<CallbackDoneFunction> ReturnCallbackDone(int status = -1337) {
116     return MakeAction(new CallbackDoneAction(status));
117 }
118 
119 class 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 
128 class 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)),
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
152 TEST_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)
159 TEST_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.
179 TEST_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.
197 TEST_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.
216 TEST_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.
231 TEST_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.
246 TEST_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.
282 TEST_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%
313 TEST_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.
336 TEST_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
355 TEST_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.
369 TEST_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
388 TEST_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
403 TEST_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.
417 TEST_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
430 TEST_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.
438 TEST_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
446 TEST_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
455 TEST_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