• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertTrue;
22 
23 import com.android.tradefed.config.Option;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
27 import com.android.tradefed.util.FileUtil;
28 import com.android.tradefed.util.StreamUtil;
29 
30 import org.junit.After;
31 import org.junit.Before;
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 
35 import java.io.BufferedInputStream;
36 import java.io.BufferedOutputStream;
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.util.zip.ZipEntry;
43 import java.util.zip.ZipOutputStream;
44 
45 @RunWith(DeviceJUnit4ClassRunner.class)
46 public class DsuGsiIntegrationTest extends DsuTestBase {
47     private static final long DSU_MAX_WAIT_SEC = 10 * 60;
48     private static final long DSU_DEFAULT_USERDATA_SIZE = 8L << 30;
49 
50     private static final String GSI_IMAGE_NAME = "system.img";
51     private static final String DSU_IMAGE_ZIP_PUSH_PATH = "/sdcard/gsi.zip";
52 
53     private static final String REMOUNT_TEST_PATH = "/system/remount_test";
54     private static final String REMOUNT_TEST_FILE = REMOUNT_TEST_PATH + "/test_file";
55 
56     @Option(
57             name = "wipe-dsu-on-failure",
58             description = "Wipe the DSU installation on test failure.")
59     private boolean mWipeDsuOnFailure = true;
60 
61     @Option(
62             name = "system-image-path",
63             description = "Path to the GSI system.img or directory containing the system.img.",
64             mandatory = true)
65     private File mSystemImagePath;
66 
67     private File mSystemImageZip;
68 
getDsuInstallCommand()69     private String getDsuInstallCommand() {
70         return String.format(
71                 "am start-activity"
72                         + " -n com.android.dynsystem/com.android.dynsystem.VerificationActivity"
73                         + " -a android.os.image.action.START_INSTALL"
74                         + " -d file://%s"
75                         + " --el KEY_USERDATA_SIZE %d"
76                         + " --ez KEY_ENABLE_WHEN_COMPLETED true",
77                 DSU_IMAGE_ZIP_PUSH_PATH, getDsuUserdataSize(DSU_DEFAULT_USERDATA_SIZE));
78     }
79 
80     @Before
setUp()81     public void setUp() throws IOException {
82         mSystemImageZip = null;
83         InputStream stream = null;
84         try {
85             assertNotNull("--system-image-path is invalid", mSystemImagePath);
86             if (mSystemImagePath.isDirectory()) {
87                 File gsiImageFile = FileUtil.findFile(mSystemImagePath, GSI_IMAGE_NAME);
88                 assertNotNull("Cannot find " + GSI_IMAGE_NAME, gsiImageFile);
89                 stream = new FileInputStream(gsiImageFile);
90             } else {
91                 stream = new FileInputStream(mSystemImagePath);
92             }
93             stream = new BufferedInputStream(stream);
94             mSystemImageZip = FileUtil.createTempFile(this.getClass().getSimpleName(), "gsi.zip");
95             try (FileOutputStream foStream = new FileOutputStream(mSystemImageZip);
96                     BufferedOutputStream boStream = new BufferedOutputStream(foStream);
97                     ZipOutputStream out = new ZipOutputStream(boStream); ) {
98                 // Don't bother compressing it as we are going to uncompress it on device anyway.
99                 out.setLevel(0);
100                 out.putNextEntry(new ZipEntry(GSI_IMAGE_NAME));
101                 StreamUtil.copyStreams(stream, out);
102                 out.closeEntry();
103             }
104         } finally {
105             StreamUtil.close(stream);
106         }
107     }
108 
109     @After
tearDown()110     public void tearDown() {
111         try {
112             FileUtil.deleteFile(mSystemImageZip);
113         } catch (SecurityException e) {
114             CLog.w("Failed to clean up '%s': %s", mSystemImageZip, e);
115         }
116         if (mWipeDsuOnFailure) {
117             // If test case completed successfully, then the test body should have called `wipe`
118             // already and calling `wipe` again would be a noop.
119             // If test case failed, then this piece of code would clean up the DSU installation left
120             // by the failed test case.
121             try {
122                 getDevice().executeShellV2Command("gsi_tool wipe");
123                 if (isDsuRunning()) {
124                     getDevice().reboot();
125                 }
126             } catch (DeviceNotAvailableException e) {
127                 CLog.w("Failed to clean up DSU installation on device: %s", e);
128             }
129         }
130         try {
131             getDevice().deleteFile(DSU_IMAGE_ZIP_PUSH_PATH);
132         } catch (DeviceNotAvailableException e) {
133             CLog.w("Failed to clean up device '%s': %s", DSU_IMAGE_ZIP_PUSH_PATH, e);
134         }
135     }
136 
137     @Test
testDsuGsi()138     public void testDsuGsi() throws DeviceNotAvailableException {
139         if (isDsuRunning()) {
140             CLog.i("Wipe existing DSU installation");
141             assertShellCommand("gsi_tool wipe");
142             getDevice().reboot();
143             assertDsuNotRunning();
144         }
145 
146         CLog.i("Pushing '%s' -> '%s'", mSystemImageZip, DSU_IMAGE_ZIP_PUSH_PATH);
147         getDevice().pushFile(mSystemImageZip, DSU_IMAGE_ZIP_PUSH_PATH, true);
148 
149         final long freeSpaceBeforeInstall = getDevice().getPartitionFreeSpace("/data") << 10;
150         assertShellCommand(getDsuInstallCommand());
151         CLog.i("Wait for DSU installation complete and reboot");
152         assertTrue(
153                 "Timed out waiting for DSU installation complete",
154                 getDevice().waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000));
155         CLog.i("DSU installation is complete and device is disconnected");
156 
157         getDevice().waitForDeviceAvailable();
158         assertDsuRunning();
159         CLog.i("Successfully booted with DSU");
160 
161         CLog.i("Test 'gsi_tool enable -s' and 'gsi_tool enable'");
162         getDevice().reboot();
163         assertDsuNotRunning();
164 
165         final long freeSpaceAfterInstall = getDevice().getPartitionFreeSpace("/data") << 10;
166         final long estimatedDsuSize = freeSpaceBeforeInstall - freeSpaceAfterInstall;
167         assertTrue(
168                 String.format(
169                         "Expected DSU installation to consume some storage space, free space before"
170                                 + " install: %d, free space after install: %d, delta: %d",
171                         freeSpaceBeforeInstall, freeSpaceAfterInstall, estimatedDsuSize),
172                 estimatedDsuSize > 0);
173 
174         assertShellCommand("gsi_tool enable");
175         getDevice().reboot();
176         assertDsuRunning();
177 
178         CLog.i("Set up 'adb remount' for testing (requires reboot)");
179         assertAdbRoot();
180         assertShellCommand("remount");
181         getDevice().reboot();
182         assertDsuRunning();
183         assertAdbRoot();
184         assertShellCommand("remount");
185         assertDevicePathNotExist(REMOUNT_TEST_PATH);
186         assertShellCommand(String.format("mkdir -p '%s'", REMOUNT_TEST_PATH));
187         assertShellCommand(String.format("cp /system/bin/init '%s'", REMOUNT_TEST_FILE));
188         final String initContent = getDevice().pullFileContents("/system/bin/init");
189 
190         CLog.i("DSU and original system have separate remount overlays");
191         assertShellCommand("gsi_tool disable");
192         getDevice().reboot();
193         assertDsuNotRunning();
194         assertDevicePathNotExist(REMOUNT_TEST_PATH);
195 
196         CLog.i("Test that 'adb remount' is consistent after reboot");
197         assertShellCommand("gsi_tool enable");
198         getDevice().reboot();
199         assertDsuRunning();
200         assertDevicePathExist(REMOUNT_TEST_FILE);
201         assertEquals(
202                 String.format(
203                         "Expected contents of '%s' to persist after reboot", REMOUNT_TEST_FILE),
204                 initContent,
205                 getDevice().pullFileContents(REMOUNT_TEST_FILE));
206 
207         CLog.i("'enable-verity' should teardown the remount overlay and restore the filesystem");
208         assertAdbRoot();
209         assertShellCommand("enable-verity");
210         getDevice().reboot();
211         assertDsuRunning();
212         assertDevicePathNotExist(REMOUNT_TEST_PATH);
213 
214         CLog.i("Test 'gsi_tool wipe'");
215         assertShellCommand("gsi_tool wipe");
216         getDevice().reboot();
217         assertDsuNotRunning();
218 
219         final double dampeningCoefficient = 0.9;
220         final long freeSpaceAfterWipe = getDevice().getPartitionFreeSpace("/data") << 10;
221         final long freeSpaceReturnedByWipe = freeSpaceAfterWipe - freeSpaceAfterInstall;
222         assertTrue(
223                 String.format(
224                         "Expected 'gsi_tool wipe' to return roughly %d of storage space, free space"
225                             + " before wipe: %d, free space after wipe: %d, delta: %d",
226                         estimatedDsuSize,
227                         freeSpaceAfterInstall,
228                         freeSpaceAfterWipe,
229                         freeSpaceReturnedByWipe),
230                 freeSpaceReturnedByWipe > (long) (estimatedDsuSize * dampeningCoefficient));
231     }
232 }
233