1 /*
2  * Copyright (C) 2019 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 package com.android.tests.dsu;
18 
19 import com.android.tradefed.build.BuildRetrievalError;
20 import com.android.tradefed.build.IBuildInfo;
21 import com.android.tradefed.build.IDeviceBuildInfo;
22 import com.android.tradefed.config.Option;
23 import com.android.tradefed.config.Option.Importance;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
26 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
27 import com.android.tradefed.util.CommandResult;
28 import com.android.tradefed.util.ZipUtil2;
29 
30 import org.apache.commons.compress.archivers.zip.ZipFile;
31 
32 import org.junit.After;
33 import org.junit.Assert;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.io.ByteArrayOutputStream;
38 import java.io.File;
39 import java.io.IOException;
40 import java.lang.Process;
41 import java.lang.Runtime;
42 import java.util.concurrent.TimeUnit;
43 
44 /**
45  * Test Dynamic System Updates by booting in and out of a supplied system image
46  */
47 @RunWith(DeviceJUnit4ClassRunner.class)
48 public class DSUEndtoEndTest extends BaseHostJUnit4Test {
49     private static final long kDefaultUserdataSize = 4L * 1024 * 1024 * 1024;
50     private static final String APK = "LockScreenAutomation.apk";
51     private static final String PACKAGE = "com.google.android.lockscreenautomation";
52     private static final String UI_AUTOMATOR_INSTRUMENTATION_RUNNER =
53         "androidx.test.uiautomator.UiAutomatorInstrumentationTestRunner";
54     private static final String CLASS = "LockScreenAutomation";
55     private static final String LPUNPACK_PATH = "bin/lpunpack";
56     private static final String SIMG2IMG_PATH = "bin/simg2img";
57 
58     // Example: atest -v DSUEndtoEndTest -- --test-arg \
59     // com.android.tradefed.testtype.HostTest:set-option:system_image_path:/full/path/to/system.img
60     @Option(name="system_image_path",
61             shortName='s',
62             description="full path to the system image to use. If not specified, attempt " +
63                         "to download the image from the test infrastructure",
64             importance=Importance.ALWAYS)
65     private String mSystemImagePath;
66 
67     @Option(name="userdata_size",
68             shortName='u',
69             description="size in bytes of the new userdata partition",
70             importance=Importance.ALWAYS)
71     private long mUserdataSize = kDefaultUserdataSize;
72 
73     private File mUnsparseSystemImage;
74 
75     @After
teardown()76     public void teardown() throws Exception {
77         uninstallPackage(PACKAGE);
78         if (mUnsparseSystemImage != null) {
79             mUnsparseSystemImage.delete();
80         }
81     }
82 
83     @Test
testDSU()84     public void testDSU() throws Exception {
85         String simg2imgPath = "simg2img";
86         if (mSystemImagePath == null) {
87             IBuildInfo buildInfo = getBuild();
88             File imgs = ((IDeviceBuildInfo) buildInfo).getDeviceImageFile();
89             Assert.assertNotEquals("Failed to fetch system image. See system_image_path parameter", null, imgs);
90             File otaTools = buildInfo.getFile("otatools.zip");
91             File tempdir = ZipUtil2.extractZipToTemp(otaTools, "otatools");
92             File system = ZipUtil2.extractFileFromZip(new ZipFile(imgs), "system.img");
93             if (system == null) {
94                 File superImg = ZipUtil2.extractFileFromZip(new ZipFile(imgs), "super.img");
95                 String lpunpackPath = new File(tempdir, LPUNPACK_PATH).getAbsolutePath();
96                 String outputDir = superImg.getParentFile().getAbsolutePath();
97                 String[] cmd = {lpunpackPath, "-p", "system_a", superImg.getAbsolutePath(), outputDir};
98                 Process p = Runtime.getRuntime().exec(cmd);
99                 p.waitFor();
100                 if (p.exitValue() == 0) {
101                     mSystemImagePath = new File(outputDir, "system_a.img").getAbsolutePath();
102                 } else {
103                     ByteArrayOutputStream stderr = new ByteArrayOutputStream();
104                     int len;
105                     byte[] buf = new byte[1024];
106                     while ((len = p.getErrorStream().read(buf)) != -1) {
107                           stderr.write(buf, 0, len);
108                     }
109                     Assert.assertEquals("non-zero exit value (" + stderr.toString("UTF-8") + ")", 0, p.exitValue());
110                 }
111             } else {
112                 mSystemImagePath = system.getAbsolutePath();
113             }
114             simg2imgPath = new File(tempdir, SIMG2IMG_PATH).getAbsolutePath();
115         }
116         File gsi = new File(mSystemImagePath);
117         Assert.assertTrue("not a valid file", gsi.isFile());
118         String[] cmd = {simg2imgPath, mSystemImagePath, mSystemImagePath + ".raw"};
119         Process p = Runtime.getRuntime().exec(cmd);
120         p.waitFor();
121         if (p.exitValue() == 0) {
122             mUnsparseSystemImage = new File(mSystemImagePath + ".raw");
123             gsi = mUnsparseSystemImage;
124         }
125 
126         boolean wasRoot = getDevice().isAdbRoot();
127         if (!wasRoot)
128             Assert.assertTrue("Test requires root", getDevice().enableAdbRoot());
129 
130         expectGsiStatus("normal");
131 
132         installPackage(APK);
133         String method = "setPin";
134         String testClass = PACKAGE + "." + CLASS;
135         String testMethod = testClass + "." + method;
136         Assert.assertTrue(testMethod + " failed.",
137             runDeviceTests(UI_AUTOMATOR_INSTRUMENTATION_RUNNER, PACKAGE, testClass, method));
138 
139         // Sleep after installing to allow time for gsi_tool to reboot. This prevents a race between
140         // the device rebooting and waitForDeviceAvailable() returning.
141         getDevice().executeShellV2Command("gsi_tool install --userdata-size " + mUserdataSize +
142             " --gsi-size " + gsi.length() + " && sleep 10000000", gsi, null, 10, TimeUnit.MINUTES, 1);
143         getDevice().waitForDeviceAvailable();
144         getDevice().enableAdbRoot();
145 
146         expectGsiStatus("running");
147 
148         rebootAndUnlock();
149 
150         expectGsiStatus("installed");
151 
152         CommandResult result = getDevice().executeShellV2Command("gsi_tool enable");
153         Assert.assertEquals("gsi_tool enable failed", 0, result.getExitCode().longValue());
154 
155         getDevice().reboot();
156 
157         expectGsiStatus("running");
158 
159         getDevice().reboot();
160 
161         expectGsiStatus("running");
162 
163         getDevice().executeShellV2Command("gsi_tool wipe");
164 
165         rebootAndUnlock();
166 
167         expectGsiStatus("normal");
168 
169         method = "removePin";
170         testClass = PACKAGE + "." + CLASS;
171         testMethod = testClass + "." + method;
172         Assert.assertTrue(testMethod + " failed.",
173             runDeviceTests(UI_AUTOMATOR_INSTRUMENTATION_RUNNER, PACKAGE, testClass, method));
174 
175         if (wasRoot) {
176             getDevice().enableAdbRoot();
177         }
178     }
179 
expectGsiStatus(String expected)180     private void expectGsiStatus(String expected) throws Exception {
181         CommandResult result = getDevice().executeShellV2Command("gsi_tool status");
182         String status = result.getStdout().split("\n", 2)[0].trim();
183         Assert.assertEquals("Device not in expected DSU state", expected, status);
184     }
185 
rebootAndUnlock()186     private void rebootAndUnlock() throws Exception {
187         getDevice().rebootUntilOnline();
188         getDevice().executeShellV2Command("input keyevent 224"); // KeyEvent.KEYCODE_WAKEUP
189         getDevice().executeShellV2Command("wm dismiss-keyguard");
190         getDevice().executeShellV2Command("input keyevent 7"); // KeyEvent.KEYCODE_0
191         getDevice().executeShellV2Command("input keyevent 7");
192         getDevice().executeShellV2Command("input keyevent 7");
193         getDevice().executeShellV2Command("input keyevent 7");
194         getDevice().executeShellV2Command("input keyevent 66"); // KeyEvent.KEYCODE_ENTER
195     }
196 }
197 
198