1 /*
2  * Copyright (C) 2010 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 package com.android.tradefed.device;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 import static org.junit.Assume.assumeFalse;
27 import static org.junit.Assume.assumeTrue;
28 import static org.mockito.Mockito.mock;
29 
30 import com.android.ddmlib.IDevice;
31 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
32 import com.android.tradefed.TestAppConstants;
33 import com.android.tradefed.log.LogUtil.CLog;
34 import com.android.tradefed.result.CollectingTestListener;
35 import com.android.tradefed.result.FileInputStreamSource;
36 import com.android.tradefed.result.InputStreamSource;
37 import com.android.tradefed.result.TestStatus;
38 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
39 import com.android.tradefed.testtype.IDeviceTest;
40 import com.android.tradefed.util.CommandResult;
41 import com.android.tradefed.util.CommandStatus;
42 import com.android.tradefed.util.FileUtil;
43 import com.android.tradefed.util.KeyguardControllerState;
44 import com.android.tradefed.util.ProcessInfo;
45 import com.android.tradefed.util.RunUtil;
46 import com.android.tradefed.util.StreamUtil;
47 
48 import org.junit.Assume;
49 import org.junit.Before;
50 import org.junit.Ignore;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 
54 import java.awt.image.BufferedImage;
55 import java.io.BufferedInputStream;
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.FileOutputStream;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.net.URLConnection;
62 import java.util.Set;
63 import java.util.concurrent.TimeUnit;
64 
65 import javax.imageio.ImageIO;
66 
67 /**
68  * Functional tests for {@link TestDevice}.
69  *
70  * <p>Requires a physical device to be connected.
71  */
72 @RunWith(DeviceJUnit4ClassRunner.class)
73 public class TestDeviceFuncTest implements IDeviceTest {
74 
75     private TestDevice mTestDevice;
76     private IDeviceStateMonitor mMonitor;
77     /** Expect bugreports to be at least a meg. */
78     private static final int MIN_BUGREPORT_BYTES = 1024 * 1024;
79 
assumeNotVirtualDevice()80     private void assumeNotVirtualDevice() {
81         assumeFalse(getDevice() instanceof RemoteAndroidDevice);
82         return;
83     }
84 
85     @Override
setDevice(ITestDevice device)86     public void setDevice(ITestDevice device) {
87         mTestDevice = (TestDevice) device;
88     }
89 
90     @Override
getDevice()91     public ITestDevice getDevice() {
92         return mTestDevice;
93     }
94 
95     @Before
setUp()96     public void setUp() throws Exception {
97         mMonitor = mTestDevice.getDeviceStateMonitor();
98         // Ensure at set-up that the device is available.
99         mTestDevice.waitForDeviceAvailable();
100     }
101 
102     /** Simple testcase to ensure that the grabbing a bugreport from a real TestDevice works. */
103     @Test
testBugreport()104     public void testBugreport() throws Exception {
105         InputStreamSource bugreport = mTestDevice.getBugreport();
106         try {
107             String data = StreamUtil.getStringFromStream(bugreport.createInputStream());
108             assertTrue(
109                     String.format(
110                             "Expected at least %d characters; only saw %d",
111                             MIN_BUGREPORT_BYTES, data.length()),
112                     data.length() >= MIN_BUGREPORT_BYTES);
113         } finally {
114             StreamUtil.cancel(bugreport);
115         }
116     }
117 
118     /** Simple testcase to ensure that the grabbing a bugreportz from a real TestDevice works. */
119     @Test
testBugreportz()120     public void testBugreportz() throws Exception {
121         if (mTestDevice.getApiLevel() < 24) {
122             CLog.i("testBugreportz() not supported by this device, skipping.");
123             return;
124         }
125         FileInputStreamSource f = null;
126         try {
127             f = (FileInputStreamSource) mTestDevice.getBugreportz();
128             assertNotNull(f);
129             FileInputStream contents = (FileInputStream) f.createInputStream();
130             assertTrue(
131                     String.format(
132                             "Expected at least %d characters; only saw %d",
133                             MIN_BUGREPORT_BYTES, contents.available()),
134                     contents.available() >= MIN_BUGREPORT_BYTES);
135         } finally {
136             StreamUtil.cancel(f);
137             if (f != null) {
138                 f.cleanFile();
139             }
140         }
141     }
142 
143     /**
144      * Simple normal case test for {@link TestDevice#executeShellCommand(String)}.
145      *
146      * <p>Do a 'shell ls' command, and verify /data and /system are listed in result.
147      */
148     @Test
testExecuteShellCommand()149     public void testExecuteShellCommand() throws DeviceNotAvailableException {
150         CLog.i("testExecuteShellCommand");
151         assertSimpleShellCommand();
152     }
153 
154     /**
155      * Verify that a simple {@link TestDevice#executeShellCommand(String)} command is successful.
156      */
assertSimpleShellCommand()157     private void assertSimpleShellCommand() throws DeviceNotAvailableException {
158         final String output = mTestDevice.executeShellCommand("ls");
159         assertTrue(output.contains("data"));
160         assertTrue(output.contains("system"));
161     }
162 
163     /** Test install and uninstall of package */
164     @Test
testInstallUninstall()165     public void testInstallUninstall() throws IOException, DeviceNotAvailableException {
166         CLog.i("testInstallUninstall");
167         // use the wifi util apk
168         File tmpFile = WifiHelper.extractWifiUtilApk();
169         try {
170             assertWifiApkInstall(tmpFile);
171         } finally {
172             FileUtil.deleteFile(tmpFile);
173         }
174     }
175 
176     /** Verifies that the given wifi util apk can be installed and uninstalled successfully */
assertWifiApkInstall(File tmpFile)177     void assertWifiApkInstall(File tmpFile) throws DeviceNotAvailableException {
178         try {
179             mTestDevice.uninstallPackage(WifiHelper.INSTRUMENTATION_PKG);
180             assertFalse(
181                     mTestDevice
182                             .getInstalledPackageNames()
183                             .contains(WifiHelper.INSTRUMENTATION_PKG));
184             assertNull(mTestDevice.installPackage(tmpFile, false));
185             assertTrue(
186                     mTestDevice
187                             .getInstalledPackageNames()
188                             .contains(WifiHelper.INSTRUMENTATION_PKG));
189             assertFalse(
190                     "apk file was not cleaned up after install",
191                     mTestDevice.doesFileExist(
192                             String.format("/data/local/tmp/%s", tmpFile.getName())));
193         } finally {
194             FileUtil.deleteFile(tmpFile);
195         }
196     }
197 
198     /** Test install and uninstall of package with spaces in file name */
199     @Test
testInstallUninstall_space()200     public void testInstallUninstall_space() throws IOException, DeviceNotAvailableException {
201         CLog.i("testInstallUninstall_space");
202 
203         File tmpFile = WifiHelper.extractWifiUtilApk();
204         File tmpFileSpaces = null;
205         try {
206             tmpFileSpaces = FileUtil.createTempFile("wifi util (3)", ".apk");
207             FileUtil.copyFile(tmpFile, tmpFileSpaces);
208             assertWifiApkInstall(tmpFileSpaces);
209         } finally {
210             FileUtil.deleteFile(tmpFile);
211             FileUtil.deleteFile(tmpFileSpaces);
212         }
213     }
214 
215     /** Push and then pull a file from device, and verify contents are as expected. */
216     @Test
testPushPull_normal()217     public void testPushPull_normal() throws IOException, DeviceNotAvailableException {
218         CLog.i("testPushPull");
219         File tmpFile = null;
220         File tmpDestFile = null;
221         String deviceFilePath = null;
222 
223         try {
224             tmpFile = createTempTestFile(null);
225             String externalStorePath = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
226             assertNotNull(externalStorePath);
227             deviceFilePath = String.format("%s/%s", externalStorePath, "tmp_testPushPull.txt");
228             // ensure file does not already exist
229             mTestDevice.deleteFile(deviceFilePath);
230             assertFalse(
231                     String.format("%s exists", deviceFilePath),
232                     mTestDevice.doesFileExist(deviceFilePath));
233 
234             assertTrue(mTestDevice.pushFile(tmpFile, deviceFilePath));
235             assertTrue(mTestDevice.doesFileExist(deviceFilePath));
236             tmpDestFile = FileUtil.createTempFile("tmp", "txt");
237             assertTrue(mTestDevice.pullFile(deviceFilePath, tmpDestFile));
238             assertTrue(compareFiles(tmpFile, tmpDestFile));
239         } finally {
240             FileUtil.deleteFile(tmpFile);
241             if (tmpDestFile != null) {
242                 tmpDestFile.delete();
243             }
244             if (deviceFilePath != null) {
245                 mTestDevice.deleteFile(deviceFilePath);
246             }
247         }
248     }
249 
250     /**
251      * Push and then pull a file from device, and verify contents are as expected.
252      *
253      * <p>This variant of the test uses "${EXTERNAL_STORAGE}" in the pathname.
254      */
255     @Test
testPushPull_extStorageVariable()256     public void testPushPull_extStorageVariable() throws IOException, DeviceNotAvailableException {
257         CLog.i("testPushPull");
258         File tmpFile = null;
259         File tmpDestFile = null;
260         File tmpDestFile2 = null;
261         String deviceFilePath = null;
262         final String filename = "tmp_testPushPull.txt";
263 
264         try {
265             tmpFile = createTempTestFile(null);
266             String externalStorePath = "${EXTERNAL_STORAGE}";
267             assertNotNull(externalStorePath);
268             deviceFilePath = String.format("%s/%s", externalStorePath, filename);
269             // ensure file does not already exist
270             mTestDevice.executeShellCommand(String.format("rm %s", deviceFilePath));
271             assertFalse(
272                     String.format("%s exists", deviceFilePath),
273                     mTestDevice.doesFileExist(deviceFilePath));
274 
275             assertTrue(mTestDevice.pushFile(tmpFile, deviceFilePath));
276             assertTrue(mTestDevice.doesFileExist(deviceFilePath));
277             tmpDestFile = FileUtil.createTempFile("tmp", "txt");
278             assertTrue(mTestDevice.pullFile(deviceFilePath, tmpDestFile));
279             assertTrue(compareFiles(tmpFile, tmpDestFile));
280 
281             tmpDestFile2 = mTestDevice.pullFileFromExternal(filename);
282             assertNotNull(tmpDestFile2);
283             assertTrue(compareFiles(tmpFile, tmpDestFile2));
284         } finally {
285             FileUtil.deleteFile(tmpFile);
286             FileUtil.deleteFile(tmpDestFile);
287             FileUtil.deleteFile(tmpDestFile2);
288             if (deviceFilePath != null) {
289                 mTestDevice.executeShellCommand(String.format("rm %s", deviceFilePath));
290             }
291         }
292     }
293 
294     /**
295      * Test pulling a file from device that does not exist.
296      *
297      * <p>Expect {@link TestDevice#pullFile(String)} to return <code>false</code>
298      */
299     @Test
testPull_noexist()300     public void testPull_noexist() throws DeviceNotAvailableException {
301         CLog.i("testPull_noexist");
302 
303         // make sure the root path is valid
304         String externalStorePath = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
305         assertNotNull(externalStorePath);
306         String deviceFilePath = String.format("%s/%s", externalStorePath, "thisfiledoesntexist");
307         assertFalse(
308                 String.format("%s exists", deviceFilePath),
309                 mTestDevice.doesFileExist(deviceFilePath));
310         assertNull(mTestDevice.pullFile(deviceFilePath));
311     }
312 
313     /**
314      * Test pulling a file from device into a local file that cannot be written to.
315      *
316      * <p>Expect {@link TestDevice#pullFile(String, File)} to return <code>false</code>
317      */
318     @Test
testPull_nopermissions()319     public void testPull_nopermissions() throws IOException, DeviceNotAvailableException {
320         Assume.assumeFalse(
321                 "Skipping test since harness is running as root.",
322                 "root".equals(System.getProperty("user.name")));
323         // Make sure the root path is valid
324         String externalStorePath = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
325         assertNotNull(externalStorePath);
326         String deviceFilePath = String.format("%s/%s", externalStorePath, "testPull_nopermissions");
327         // first push a file so we have something to retrieve
328         assertTrue(mTestDevice.pushString("test data", deviceFilePath));
329         assertTrue(
330                 String.format("%s does not exist", deviceFilePath),
331                 mTestDevice.doesFileExist(deviceFilePath));
332         File tmpFile = null;
333         try {
334             tmpFile = FileUtil.createTempFile("testPull_nopermissions", ".txt");
335             tmpFile.setReadOnly();
336             assertFalse(mTestDevice.pullFile(deviceFilePath, tmpFile));
337         } finally {
338             if (tmpFile != null) {
339                 tmpFile.setWritable(true);
340                 FileUtil.deleteFile(tmpFile);
341             }
342         }
343     }
344 
345     /**
346      * Test pushing a file onto device that does not exist.
347      *
348      * <p>Expect {@link TestDevice#pushFile(File, String)} to return <code>false</code>
349      */
350     @Test
testPush_noexist()351     public void testPush_noexist() throws DeviceNotAvailableException {
352         CLog.i("testPush_noexist");
353 
354         // make sure the root path is valid
355         String externalStorePath = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
356         assertNotNull(externalStorePath);
357         String deviceFilePath = String.format("%s/%s", externalStorePath, "remotepath");
358         assertFalse(mTestDevice.pushFile(new File("idontexist"), deviceFilePath));
359     }
360 
createTempTestFile(File dir)361     private File createTempTestFile(File dir) throws IOException {
362         File tmpFile = null;
363         try {
364             final String fileContents = "this is the test file contents";
365             tmpFile = FileUtil.createTempFile("tmp", ".txt", dir);
366             FileUtil.writeToFile(fileContents, tmpFile);
367             return tmpFile;
368         } catch (IOException e) {
369             if (tmpFile != null) {
370                 tmpFile.delete();
371             }
372             throw e;
373         }
374     }
375 
376     /** Utility method to do byte-wise content comparison of two files. */
compareFiles(File file1, File file2)377     private boolean compareFiles(File file1, File file2) throws IOException {
378         BufferedInputStream stream1 = null;
379         BufferedInputStream stream2 = null;
380 
381         try {
382             stream1 = new BufferedInputStream(new FileInputStream(file1));
383             stream2 = new BufferedInputStream(new FileInputStream(file2));
384             boolean eof = false;
385             while (!eof) {
386                 int byte1 = stream1.read();
387                 int byte2 = stream2.read();
388                 if (byte1 != byte2) {
389                     return false;
390                 }
391                 eof = byte1 == -1;
392             }
393             return true;
394         } finally {
395             StreamUtil.close(stream1);
396             StreamUtil.close(stream2);
397         }
398     }
399 
400     /**
401      * Make sure that we can correctly index directories that have a symlink in the middle. This
402      * verifies a ddmlib bugfix which added/fixed this functionality.
403      */
404     @Test
testListSymlinkDir()405     public void testListSymlinkDir() throws Exception {
406         final String extStore = "/data/local";
407 
408         // Clean up after potential failed run
409         mTestDevice.deleteFile(String.format("%s/testdir", extStore));
410         mTestDevice.deleteFile(String.format("%s/testdir2/foo.txt", extStore));
411         mTestDevice.deleteFile(String.format("%s/testdir2", extStore));
412 
413         try {
414             assertEquals(
415                     "",
416                     mTestDevice.executeShellCommand(String.format("mkdir %s/testdir2", extStore)));
417             assertEquals(
418                     "",
419                     mTestDevice.executeShellCommand(
420                             String.format("touch %s/testdir2/foo.txt", extStore)));
421             assertEquals(
422                     "",
423                     mTestDevice.executeShellCommand(
424                             String.format("ln -s %s/testdir2 %s/testdir", extStore, extStore)));
425 
426             assertNotNull(mTestDevice.getFileEntry(String.format("%s/testdir/foo.txt", extStore)));
427         } finally {
428             mTestDevice.deleteFile(String.format("%s/testdir", extStore));
429             mTestDevice.deleteFile(String.format("%s/testdir2/foo.txt", extStore));
430             mTestDevice.deleteFile(String.format("%s/testdir2", extStore));
431         }
432     }
433 
434     /** Test syncing a single file using {@link TestDevice#syncFiles(File, String)}. */
435     @Ignore
436     @Test
testSyncFiles_normal()437     public void testSyncFiles_normal() throws Exception {
438         doTestSyncFiles(mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE));
439     }
440 
441     /**
442      * Test syncing a single file using {@link TestDevice#syncFiles(File, String)}.
443      *
444      * <p>This variant of the test uses "${EXTERNAL_STORAGE}" in the pathname.
445      */
446     @Ignore
447     @Test
testSyncFiles_extStorageVariable()448     public void testSyncFiles_extStorageVariable() throws Exception {
449         doTestSyncFiles("${EXTERNAL_STORAGE}");
450     }
451 
452     /** Test syncing a single file using {@link TestDevice#syncFiles(File, String)}. */
doTestSyncFiles(String externalStorePath)453     private void doTestSyncFiles(String externalStorePath) throws Exception {
454         String expectedDeviceFilePath = null;
455 
456         // create temp dir with one temp file
457         File tmpDir = FileUtil.createTempDir("tmp");
458         try {
459             File tmpFile = createTempTestFile(tmpDir);
460             // set last modified to 10 minutes ago
461             tmpFile.setLastModified(System.currentTimeMillis() - 10 * 60 * 1000);
462             assertNotNull(externalStorePath);
463             expectedDeviceFilePath =
464                     String.format(
465                             "%s/%s/%s", externalStorePath, tmpDir.getName(), tmpFile.getName());
466 
467             assertTrue(mTestDevice.syncFiles(tmpDir, externalStorePath));
468             assertTrue(mTestDevice.doesFileExist(expectedDeviceFilePath));
469 
470             // get 'ls -l' attributes of file which includes timestamp
471             String origTmpFileStamp =
472                     mTestDevice.executeShellCommand(
473                             String.format("ls -l %s", expectedDeviceFilePath));
474             // now create another file and verify that is synced
475             File tmpFile2 = createTempTestFile(tmpDir);
476             tmpFile2.setLastModified(System.currentTimeMillis() - 10 * 60 * 1000);
477             assertTrue(mTestDevice.syncFiles(tmpDir, externalStorePath));
478             String expectedDeviceFilePath2 =
479                     String.format(
480                             "%s/%s/%s", externalStorePath, tmpDir.getName(), tmpFile2.getName());
481             assertTrue(mTestDevice.doesFileExist(expectedDeviceFilePath2));
482 
483             // verify 1st file timestamp did not change
484             String unchangedTmpFileStamp =
485                     mTestDevice.executeShellCommand(
486                             String.format("ls -l %s", expectedDeviceFilePath));
487             assertEquals(origTmpFileStamp, unchangedTmpFileStamp);
488 
489             // now modify 1st file and verify it does change remotely
490             String testString = "blah";
491             FileOutputStream stream = new FileOutputStream(tmpFile);
492             stream.write(testString.getBytes());
493             stream.close();
494 
495             assertTrue(mTestDevice.syncFiles(tmpDir, externalStorePath));
496             String tmpFileContents =
497                     mTestDevice.executeShellCommand(
498                             String.format("cat %s", expectedDeviceFilePath));
499             assertTrue(tmpFileContents.contains(testString));
500         } finally {
501             if (expectedDeviceFilePath != null && externalStorePath != null) {
502                 // note that expectedDeviceFilePath has externalStorePath prepended at definition
503                 mTestDevice.deleteFile(expectedDeviceFilePath);
504             }
505             FileUtil.recursiveDelete(tmpDir);
506         }
507     }
508 
509     /** Test pushing a directory */
510     @Test
testPushDir()511     public void testPushDir() throws IOException, DeviceNotAvailableException {
512         String expectedDeviceFilePath = null;
513         String externalStorePath = null;
514         File rootDir = FileUtil.createTempDir("tmp");
515         // create temp dir with one temp file
516         try {
517             File tmpDir = FileUtil.createTempDir("tmp", rootDir);
518             File tmpFile = createTempTestFile(tmpDir);
519             externalStorePath = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
520             assertNotNull(externalStorePath);
521             expectedDeviceFilePath =
522                     String.format(
523                             "%s/%s/%s", externalStorePath, tmpDir.getName(), tmpFile.getName());
524 
525             assertTrue(mTestDevice.pushDir(rootDir, externalStorePath));
526             assertTrue(mTestDevice.doesFileExist(expectedDeviceFilePath));
527 
528         } finally {
529             if (expectedDeviceFilePath != null && externalStorePath != null) {
530                 mTestDevice.deleteFile(
531                         String.format("%s/%s", externalStorePath, expectedDeviceFilePath));
532             }
533             FileUtil.recursiveDelete(rootDir);
534         }
535     }
536 
537     /**
538      * Test {@link TestDevice#executeFastbootCommand(String...)} when device is in adb mode.
539      *
540      * <p>Expect fastboot recovery to be invoked, which will boot device back to fastboot mode and
541      * command will succeed.
542      */
543     @Test
testExecuteFastbootCommand_deviceInAdb()544     public void testExecuteFastbootCommand_deviceInAdb() throws DeviceNotAvailableException {
545         CLog.i("testExecuteFastbootCommand_deviceInAdb");
546         if (!mTestDevice.isFastbootEnabled()) {
547             CLog.i("Fastboot not enabled skipping testExecuteFastbootCommand_deviceInAdb");
548             return;
549         }
550 
551         assumeNotVirtualDevice();
552 
553         long origTimeout = mTestDevice.getCommandTimeout();
554         try {
555             assertEquals(TestDeviceState.ONLINE, mMonitor.getDeviceState());
556             // reset operation timeout to small value to make test run quicker
557             mTestDevice.setCommandTimeout(5 * 1000);
558             assertEquals(
559                     CommandStatus.SUCCESS,
560                     mTestDevice.executeFastbootCommand("getvar", "product").getStatus());
561             assertEquals(TestDeviceState.FASTBOOT, mMonitor.getDeviceState());
562         } finally {
563             mTestDevice.setCommandTimeout(origTimeout);
564             mTestDevice.reboot();
565             assertEquals(TestDeviceState.ONLINE, mMonitor.getDeviceState());
566         }
567     }
568 
569     /**
570      * Test {@link TestDevice#executeFastbootCommand(String...)} when an invalid command is passed.
571      *
572      * <p>Expect the result indicate failure, and recovery not to be invoked.
573      */
574     @Test
testExecuteFastbootCommand_badCommand()575     public void testExecuteFastbootCommand_badCommand() throws DeviceNotAvailableException {
576         CLog.i("testExecuteFastbootCommand_badCommand");
577         if (!mTestDevice.isFastbootEnabled()) {
578             CLog.i("Fastboot not enabled skipping testExecuteFastbootCommand_badCommand");
579             return;
580         }
581 
582         assumeNotVirtualDevice();
583 
584         IDeviceRecovery origRecovery = mTestDevice.getRecovery();
585         try {
586             mTestDevice.rebootIntoBootloader();
587             assertEquals(TestDeviceState.FASTBOOT, mMonitor.getDeviceState());
588             // substitute recovery mechanism to ensure recovery is not called when bad command
589             // is passed
590             IDeviceRecovery mockRecovery = mock(IDeviceRecovery.class);
591             mTestDevice.setRecovery(mockRecovery);
592             assertEquals(
593                     CommandStatus.FAILED,
594                     mTestDevice.executeFastbootCommand("badcommand").getStatus());
595         } finally {
596             mTestDevice.setRecovery(origRecovery);
597             mTestDevice.reboot();
598             assertEquals(TestDeviceState.ONLINE, mMonitor.getDeviceState());
599         }
600     }
601 
602     /** Verify device can be rebooted into bootloader and back to adb. */
603     @Test
testRebootIntoBootloader()604     public void testRebootIntoBootloader() throws DeviceNotAvailableException {
605         CLog.i("testRebootIntoBootloader");
606         if (!mTestDevice.isFastbootEnabled()) {
607             CLog.i("Fastboot not enabled skipping testRebootInBootloader");
608             return;
609         }
610 
611         assumeNotVirtualDevice();
612 
613         try {
614             mTestDevice.rebootIntoBootloader();
615             assertEquals(TestDeviceState.FASTBOOT, mMonitor.getDeviceState());
616         } finally {
617             mTestDevice.reboot();
618             assertEquals(TestDeviceState.ONLINE, mMonitor.getDeviceState());
619         }
620     }
621 
622     /** Verify device can be rebooted into adb. */
623     @Test
testReboot()624     public void testReboot() throws DeviceNotAvailableException {
625         CLog.i("testReboot");
626         mTestDevice.reboot();
627         assertEquals(TestDeviceState.ONLINE, mMonitor.getDeviceState());
628         // check that device has root
629         assertTrue(mTestDevice.executeShellCommand("id").contains("root"));
630     }
631 
632     /** Verify device can be rebooted into adb recovery. */
633     @Test
testRebootIntoRecovery()634     public void testRebootIntoRecovery() throws Exception {
635         CLog.i("testRebootIntoRecovery");
636         if (!mTestDevice.isFastbootEnabled()) {
637             CLog.i("Fastboot not enabled skipping testRebootInRecovery");
638             return;
639         }
640 
641         assumeNotVirtualDevice();
642 
643         try {
644             mTestDevice.rebootIntoRecovery();
645             assertEquals(TestDeviceState.RECOVERY, mMonitor.getDeviceState());
646         } finally {
647             // Recovery is a special case to recover from, we need to call reboot on the idevice.
648             RunUtil.getDefault().sleep(15 * 1000);
649             getDevice().getIDevice().reboot(null);
650         }
651     }
652 
waitForSystemServerProcess()653     private ProcessInfo waitForSystemServerProcess() throws DeviceNotAvailableException {
654         ProcessInfo systemServer = null;
655         for (int i = 0; i < 5; i++) {
656             systemServer = mTestDevice.getProcessByName("system_server");
657             if (systemServer != null) {
658                 return systemServer;
659             }
660             RunUtil.getDefault().sleep(1000);
661         }
662         CLog.i("The system_server process fails to come up");
663         return null;
664     }
665 
666     /** Test device soft-restart detection API. */
667     @Test
testDeviceSoftRestart()668     public void testDeviceSoftRestart() throws DeviceNotAvailableException {
669         CLog.i("testDeviceSoftRestartSince");
670 
671         // API 29 was the first to support soft reboot detection in this way
672         assumeTrue(
673                 "Test only valid for devices at or above API level 29",
674                 mTestDevice.getApiLevel() >= 29);
675 
676         // Get system_server process info
677         ProcessInfo prev = mTestDevice.getProcessByName("system_server");
678         long deviceTimeMs = mTestDevice.getDeviceDate();
679         if (prev == null) {
680             CLog.i("System_server process does not exist. Abort testDeviceSoftRestart.");
681             return;
682         }
683         assertFalse(mTestDevice.deviceSoftRestartedSince(prev.getStartTime(), TimeUnit.SECONDS));
684         assertFalse(mTestDevice.deviceSoftRestarted(prev));
685         if (!mTestDevice.isAdbRoot()) {
686             mTestDevice.enableAdbRoot();
687         }
688         mTestDevice.executeShellCommand(String.format("kill %s", prev.getPid()));
689         RunUtil.getDefault().sleep(1000);
690         assertTrue(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
691         assertTrue(mTestDevice.deviceSoftRestarted(prev));
692         prev = waitForSystemServerProcess();
693         deviceTimeMs = mTestDevice.getDeviceDate();
694         // Sleep for a second to ensure the reboot happens in at least the second after
695         // we took this timestamp
696         RunUtil.getDefault().sleep(1000);
697         mTestDevice.reboot();
698         if (!mTestDevice.isAdbRoot()) {
699             mTestDevice.enableAdbRoot();
700         }
701         assertFalse(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
702         assertFalse(mTestDevice.deviceSoftRestarted(prev));
703         // Restart system_server 10 seconds after reboot
704         RunUtil.getDefault().sleep(10000);
705         mTestDevice.executeShellCommand(
706                 String.format("kill %s", mTestDevice.getProcessByName("system_server").getPid()));
707         RunUtil.getDefault().sleep(1000);
708         assertTrue(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
709         assertTrue(mTestDevice.deviceSoftRestarted(prev));
710         waitForSystemServerProcess();
711         assertTrue(mTestDevice.deviceSoftRestartedSince(deviceTimeMs, TimeUnit.MILLISECONDS));
712         assertTrue(mTestDevice.deviceSoftRestarted(prev));
713     }
714 
715     /**
716      * Verify that {@link TestDevice#clearErrorDialogs()} can successfully clear an error dialog
717      * from screen.
718      *
719      * <p>This is done by running a test app which will crash, then running another app that does UI
720      * based tests.
721      *
722      * <p>Assumes DevTools and TradeFedUiApp are currently installed.
723      */
724     @Ignore
725     @Test
testClearErrorDialogs_crash()726     public void testClearErrorDialogs_crash() throws DeviceNotAvailableException {
727         CLog.i("testClearErrorDialogs_crash");
728         // Ensure device is in a known state, we doing extra care here otherwise it may be flaky
729         int retry = 5;
730         mTestDevice.disableKeyguard();
731         while (!runUITests()) {
732             getDevice().reboot();
733             mTestDevice.waitForDeviceAvailable();
734             mTestDevice.disableKeyguard();
735             RunUtil.getDefault().sleep(2000);
736             if (retry == 0) {
737                 fail("Fail to setup the device in a known state");
738             }
739             retry--;
740         }
741         // now cause a crash dialog to appear
742         getDevice().executeShellCommand("am start -n " + TestAppConstants.CRASH_ACTIVITY);
743         RunUtil.getDefault().sleep(2000);
744         // Ensure the error dialogue is here
745         assertFalse(runUITests());
746         RunUtil.getDefault().sleep(2000);
747 
748         assertTrue("Clear dialogs did not clear anything", getDevice().clearErrorDialogs());
749         assertTrue(runUITests());
750     }
751 
752     /**
753      * Verify the steps taken to disable keyguard after reboot are successfully
754      *
755      * <p>This is done by rebooting then run a app that does UI based tests.
756      *
757      * <p>Assumes DevTools and TradeFedUiApp are currently installed.
758      */
759     @Test
testDisableKeyguard()760     public void testDisableKeyguard() throws DeviceNotAvailableException {
761         CLog.i("testDisableKeyguard");
762         getDevice().reboot();
763         mTestDevice.waitForDeviceAvailable();
764         // Bump from 3000 to reduce risk of racing "wm dismiss-keyguard"
765         RunUtil.getDefault().sleep(5000);
766         KeyguardControllerState keyguard = mTestDevice.getKeyguardState();
767         if (keyguard == null) {
768             // If the getKeyguardState is not supported.
769             assertTrue(runUITests());
770         } else {
771             assertFalse(keyguard.isKeyguardShowing());
772         }
773     }
774 
775     /** Test that TradeFed can successfully recover from the adb host daemon process being killed */
776     @Test
testExecuteShellCommand_adbKilled()777     public void testExecuteShellCommand_adbKilled() {
778         // FIXME: adb typically does not recover, and this causes rest of tests to fail
779         // Log.i(LOG_TAG, "testExecuteShellCommand_adbKilled");
780         // CommandResult result = RunUtil.getInstance().runTimedCmd(30*1000, "adb", "kill-server");
781         // assertEquals(CommandStatus.SUCCESS, result.getStatus());
782         // assertSimpleShellCommand();
783     }
784 
785     /**
786      * Basic test for {@link TestDevice#getScreenshot()}.
787      *
788      * <p>Grab a screenshot, save it to a file, and perform a cursory size check to ensure its
789      * valid.
790      */
791     @Test
testGetScreenshot()792     public void testGetScreenshot() throws DeviceNotAvailableException, IOException {
793         CLog.i("testGetScreenshot");
794         InputStreamSource source = getDevice().getScreenshot();
795         assertNotNull(source);
796         File tmpPngFile = FileUtil.createTempFile("screenshot", ".png");
797         try {
798             FileUtil.writeToFile(source.createInputStream(), tmpPngFile);
799             CLog.i("Created file at %s", tmpPngFile.getAbsolutePath());
800             // Decode the content, will return null if not an image.
801             BufferedImage image = ImageIO.read(tmpPngFile);
802             assertNotNull(image);
803             // All our device screenshot should be bigger than 200px
804             assertTrue(image.getWidth() > 200);
805             assertTrue(image.getHeight() > 200);
806         } finally {
807             FileUtil.deleteFile(tmpPngFile);
808             source.close();
809         }
810     }
811 
812     /**
813      * Basic test for {@link TestDevice#getLogcat(int)}.
814      *
815      * <p>Dumps a bunch of messages to logcat, calls getLogcat(), and verifies size of capture file
816      * is equal to provided data.
817      */
818     @Test
testGetLogcat_size()819     public void testGetLogcat_size() throws DeviceNotAvailableException, IOException {
820         CLog.i("testGetLogcat_size");
821         for (int i = 0; i < 100; i++) {
822             getDevice().executeShellCommand(String.format("log testGetLogcat_size log dump %d", i));
823         }
824         // sleep a small amount of time to ensure last log message makes it into capture
825         RunUtil.getDefault().sleep(500);
826         File tmpTxtFile = FileUtil.createTempFile("logcat", ".txt");
827         try (InputStreamSource source = getDevice().getLogcatDump()) {
828             assertNotNull(source);
829             FileUtil.writeToFile(source.createInputStream(), tmpTxtFile);
830             CLog.i("Created file at %s", tmpTxtFile.getAbsolutePath());
831             // Check we have at least our 100 lines.
832             assertTrue(
833                     "Saved text file is smaller than expected", 100 * 1024 <= tmpTxtFile.length());
834             // ensure last log message is present in log
835             String s = FileUtil.readStringFromFile(tmpTxtFile);
836             assertTrue(
837                     "last log message is not in captured logcat",
838                     s.contains("testGetLogcat_size log dump 99"));
839         } finally {
840             FileUtil.deleteFile(tmpTxtFile);
841         }
842     }
843 
844     /**
845      * Basic test for testing LOGCAT_CMD backward compatibility.
846      *
847      * <p>Checks if future changes to LOGCAT_CMD does not break APIs.
848      */
849     @Test
testLogcatCmd()850     public void testLogcatCmd() throws DeviceNotAvailableException {
851         CLog.i("testLogcatCmd");
852         // Adding -d flag to dump the log and exit, to make this a non-blocking call
853         CommandResult result =
854                 mTestDevice.executeShellV2Command(
855                         LogcatReceiver.getDefaultLogcatCmd(mTestDevice) + " -d");
856         assertThat(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
857     }
858 
859     /** Test that {@link TestDevice#getProperty(String)} works after a reboot. */
860     @Test
testGetProperty()861     public void testGetProperty() throws Exception {
862         assertNotNull(getDevice().getProperty(DeviceProperties.SDK_VERSION));
863         getDevice().rebootUntilOnline();
864         assertNotNull(getDevice().getProperty(DeviceProperties.SDK_VERSION));
865     }
866 
867     /** Test that {@link TestDevice#getProperty(String)} works for volatile properties. */
868     @Test
testGetProperty_volatile()869     public void testGetProperty_volatile() throws Exception {
870         getDevice().setProperty("prop.test", "0");
871         assertEquals("0", getDevice().getProperty("prop.test"));
872         getDevice().setProperty("prop.test", "1");
873         assertEquals("1", getDevice().getProperty("prop.test"));
874     }
875 
876     /** Test that the recovery mechanism works in {@link TestDevice#getFileEntry(String)} */
877     @Test
testGetFileEntry_recovery()878     public void testGetFileEntry_recovery() throws Exception {
879         if (!mTestDevice.isFastbootEnabled()) {
880             CLog.i("Fastboot not enabled skipping testGetFileEntry_recovery");
881             return;
882         }
883 
884         // Recovery not implemented on Cuttlefish / Goldfish
885         assumeNotVirtualDevice();
886 
887         try {
888             getDevice().rebootIntoBootloader();
889             // expect recovery to kick in, and reboot device back to adb so the call works
890             IFileEntry entry = getDevice().getFileEntry("/data");
891             assertNotNull(entry);
892         } finally {
893             getDevice().reboot();
894         }
895     }
896 
897     /** Test that {@link TestDevice#pullFileContents} works correctly. */
898     @Test
testPullFileContents()899     public void testPullFileContents() throws Exception {
900         final String path = "/data/misc_ce/0/apexrollback/foo/test_file.txt";
901         final String content = "The quick brown fox jumps over the lazy dog";
902         try {
903             // need root to access files under /data/misc_ce
904             mTestDevice.enableAdbRoot();
905             assertTrue(mTestDevice.pushString(content, path));
906             // check the content is the same as what we just pushed
907             assertEquals(content, mTestDevice.pullFileContents(path));
908             mTestDevice.reboot();
909             // check the content remains the same after reboot
910             assertEquals(content, mTestDevice.pullFileContents(path));
911         } finally {
912             mTestDevice.disableAdbRoot();
913         }
914     }
915 
916     /** Run the test app UI tests and return true if they all pass. */
runUITests()917     private boolean runUITests() throws DeviceNotAvailableException {
918         RemoteAndroidTestRunner uirunner =
919                 new RemoteAndroidTestRunner(
920                         TestAppConstants.UITESTAPP_PACKAGE, getDevice().getIDevice());
921         CollectingTestListener uilistener = new CollectingTestListener();
922         getDevice().runInstrumentationTests(uirunner, uilistener);
923         return TestAppConstants.UI_TOTAL_TESTS == uilistener.getNumTestsInState(TestStatus.PASSED);
924     }
925 
926     /** Test for {@link NativeDevice#setSetting(int, String, String, String)} */
927     @Test
testPutSettings()928     public void testPutSettings() throws Exception {
929         String initValue = mTestDevice.getSetting(0, "system", "screen_brightness");
930         CLog.i("initial value was: %s", initValue);
931         assertTrue(!initValue.equals("50"));
932         mTestDevice.setSetting(0, "system", "screen_brightness", "50");
933         String secondValue = mTestDevice.getSetting(0, "system", "screen_brightness");
934         assertEquals("50", secondValue);
935         // restore initial value
936         mTestDevice.setSetting(0, "system", "screen_brightness", initValue);
937     }
938 
939     /** Test for {@link TestDevice#getScreenshot()}. */
940     @Test
testScreenshot()941     public void testScreenshot() throws Exception {
942         InputStreamSource screenshot = mTestDevice.getScreenshot();
943         assertNotNull(screenshot);
944         try (InputStream stream = screenshot.createInputStream();
945                 BufferedInputStream bs = new BufferedInputStream(stream)) {
946             assertEquals("image/png", URLConnection.guessContentTypeFromStream(bs));
947         } finally {
948             StreamUtil.close(screenshot);
949         }
950     }
951 
952     /** Test for {@link TestDevice#getScreenshot(long)}. */
953     @Test
testScreenshot_withDisplay()954     public void testScreenshot_withDisplay() throws Exception {
955         Set<Long> displays = mTestDevice.listDisplayIds();
956         assertThat(displays).isNotEmpty();
957         InputStreamSource screenshot = mTestDevice.getScreenshot(displays.iterator().next());
958         assertNotNull(screenshot);
959         try (InputStream stream = screenshot.createInputStream();
960                 BufferedInputStream bs = new BufferedInputStream(stream)) {
961             assertEquals("image/png", URLConnection.guessContentTypeFromStream(bs));
962         } finally {
963             StreamUtil.close(screenshot);
964         }
965     }
966 }
967