• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.apex;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.cts.install.lib.host.InstallUtilsHost;
25 
26 import com.android.apex.ApexInfo;
27 import com.android.apex.XmlParser;
28 import com.android.tests.rollback.host.AbandonSessionsRule;
29 import com.android.tradefed.device.ITestDevice;
30 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
32 
33 import org.junit.After;
34 import org.junit.Before;
35 import org.junit.Rule;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.time.Duration;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.stream.Collectors;
45 
46 /**
47  * Host side integration tests for apexd.
48  */
49 @RunWith(DeviceJUnit4ClassRunner.class)
50 public class ApexdHostTest extends BaseHostJUnit4Test  {
51 
52     private static final String SHIM_APEX_PATH = "/system/apex/com.android.apex.cts.shim.apex";
53 
54     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
55 
56     @Rule
57     public AbandonSessionsRule mHostTestRule = new AbandonSessionsRule(this);
58 
59     private boolean mWasAdbRoot = false;
60 
61     @Before
setUp()62     public void setUp() throws Exception {
63         mHostUtils.uninstallShimApexIfNecessary();
64         mWasAdbRoot = getDevice().isAdbRoot();
65         if (!mWasAdbRoot) {
66             assumeTrue("Device requires root", getDevice().enableAdbRoot());
67         }
68     }
69 
70     @After
tearDown()71     public void tearDown() throws Exception {
72         mHostUtils.uninstallShimApexIfNecessary();
73         if (!mWasAdbRoot) {
74             getDevice().disableAdbRoot();
75         }
76     }
77 
78     @Test
testOrphanedApexIsNotActivated()79     public void testOrphanedApexIsNotActivated() throws Exception {
80         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
81         assumeTrue("Device requires root", getDevice().isAdbRoot());
82         try {
83             assertThat(getDevice().pushFile(mHostUtils.getTestFile("apex.apexd_test_v2.apex"),
84                     "/data/apex/active/apexd_test_v2.apex")).isTrue();
85             getDevice().reboot();
86             assertWithMessage("Timed out waiting for device to boot").that(
87                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
88             final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
89             ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
90                     "com.android.apex.test_package", 2L);
91             assertThat(activeApexes).doesNotContain(testApex);
92             mHostUtils.waitForFileDeleted("/data/apex/active/apexd_test_v2.apex",
93                     Duration.ofMinutes(3));
94         } finally {
95             getDevice().executeShellV2Command("rm /data/apex/active/apexd_test_v2.apex");
96         }
97     }
98     @Test
testApexWithoutPbIsNotActivated()99     public void testApexWithoutPbIsNotActivated() throws Exception {
100         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
101         assumeTrue("Device requires root", getDevice().isAdbRoot());
102         final String testApexFile = "com.android.apex.cts.shim.v2_no_pb.apex";
103         try {
104             assertThat(getDevice().pushFile(mHostUtils.getTestFile(testApexFile),
105                     "/data/apex/active/" + testApexFile)).isTrue();
106             getDevice().reboot();
107             assertWithMessage("Timed out waiting for device to boot").that(
108                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
109             final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
110             ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
111                     "com.android.apex.cts.shim", 2L);
112             assertThat(activeApexes).doesNotContain(testApex);
113             mHostUtils.waitForFileDeleted("/data/apex/active/" + testApexFile,
114                     Duration.ofMinutes(3));
115         } finally {
116             getDevice().executeShellV2Command("rm /data/apex/active/" + testApexFile);
117         }
118     }
119 
120     @Test
testRemountApex()121     public void testRemountApex() throws Exception {
122         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
123         assumeTrue("Device requires root", getDevice().isAdbRoot());
124         final File oldFile = getDevice().pullFile(SHIM_APEX_PATH);
125         try {
126             getDevice().remountSystemWritable();
127             // In case remount requires a reboot, wait for boot to complete.
128             getDevice().waitForBootComplete(Duration.ofMinutes(3).toMillis());
129             final File newFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
130             // Stop framework
131             getDevice().executeShellV2Command("stop");
132             // Push new shim APEX. This simulates adb sync.
133             getDevice().pushFile(newFile, SHIM_APEX_PATH);
134             // Ask apexd to remount packages
135             getDevice().executeShellV2Command("cmd -w apexservice remountPackages");
136             // Start framework
137             getDevice().executeShellV2Command("start");
138             // Give enough time for system_server to boot.
139             Thread.sleep(Duration.ofSeconds(15).toMillis());
140             final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
141             ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
142                     "com.android.apex.cts.shim", 2L);
143             assertThat(activeApexes).contains(testApex);
144         } finally {
145             getDevice().pushFile(oldFile, SHIM_APEX_PATH);
146             getDevice().reboot();
147         }
148     }
149 
150     @Test
testApexWithoutPbIsNotActivated_ProductPartitionHasOlderVersion()151     public void testApexWithoutPbIsNotActivated_ProductPartitionHasOlderVersion()
152             throws Exception {
153         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
154         assumeTrue("Device requires root", getDevice().isAdbRoot());
155 
156         try {
157             getDevice().remountSystemWritable();
158             // In case remount requires a reboot, wait for boot to complete.
159             assertWithMessage("Timed out waiting for device to boot").that(
160                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
161 
162             final File v1 = mHostUtils.getTestFile("apex.apexd_test.apex");
163             getDevice().pushFile(v1, "/product/apex/apex.apexd_test.apex");
164 
165             final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex");
166             getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex");
167 
168             getDevice().reboot();
169             assertWithMessage("Timed out waiting for device to boot").that(
170                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
171 
172             final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
173             assertThat(activeApexes).contains(new ITestDevice.ApexInfo(
174                     "com.android.apex.test_package", 1L));
175             assertThat(activeApexes).doesNotContain(new ITestDevice.ApexInfo(
176                     "com.android.apex.test_package", 2L));
177 
178             // v2_no_pb should be deleted
179             mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex",
180                     Duration.ofMinutes(3));
181         } finally {
182             getDevice().remountSystemWritable();
183             assertWithMessage("Timed out waiting for device to boot").that(
184                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
185 
186             getDevice().executeShellV2Command("rm /product/apex/apex.apexd_test.apex");
187             getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex");
188         }
189     }
190 
191     @Test
testApexWithoutPbIsNotActivated_ProductPartitionHasNewerVersion()192     public void testApexWithoutPbIsNotActivated_ProductPartitionHasNewerVersion()
193             throws Exception {
194         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
195         assumeTrue("Device requires root", getDevice().isAdbRoot());
196 
197         try {
198             getDevice().remountSystemWritable();
199             // In case remount requires a reboot, wait for boot to complete.
200             assertWithMessage("Timed out waiting for device to boot").that(
201                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
202 
203             final File v3 = mHostUtils.getTestFile("apex.apexd_test_v3.apex");
204             getDevice().pushFile(v3, "/product/apex/apex.apexd_test_v3.apex");
205 
206             final File v2_no_pb = mHostUtils.getTestFile("apex.apexd_test_v2_no_pb.apex");
207             getDevice().pushFile(v2_no_pb, "/data/apex/active/apex.apexd_test_v2_no_pb.apex");
208 
209             getDevice().reboot();
210             assertWithMessage("Timed out waiting for device to boot").that(
211                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
212 
213             final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
214             assertThat(activeApexes).contains(new ITestDevice.ApexInfo(
215                     "com.android.apex.test_package", 3L));
216             assertThat(activeApexes).doesNotContain(new ITestDevice.ApexInfo(
217                     "com.android.apex.test_package", 2L));
218 
219             // v2_no_pb should be deleted
220             mHostUtils.waitForFileDeleted("/data/apex/active/apex.apexd_test_v2_no_pb.apex",
221                     Duration.ofMinutes(3));
222         } finally {
223             getDevice().remountSystemWritable();
224             assertWithMessage("Timed out waiting for device to boot").that(
225                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
226 
227             getDevice().executeShellV2Command("rm /product/apex/apex.apexd_test_v3.apex");
228             getDevice().executeShellV2Command("rm /data/apex/active/apex.apexd_test_v2_no_pb.apex");
229         }
230     }
231 
232     @Test
testApexInfoListIsValid()233     public void testApexInfoListIsValid() throws Exception {
234         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
235         assumeTrue("Device requires root", getDevice().isAdbRoot());
236 
237         try (FileInputStream fis = new FileInputStream(
238                 getDevice().pullFile("/apex/apex-info-list.xml"))) {
239             // #1 Data got from apexd via binder
240             Set<ITestDevice.ApexInfo> fromApexd = getDevice().getActiveApexes();
241             // #2 Data got from the xml file (key is the path)
242             Map<String, ApexInfo> fromXml = XmlParser.readApexInfoList(fis).getApexInfo().stream()
243                     .collect(Collectors.toMap(ApexInfo::getModulePath, ai -> ai));
244 
245             // Make sure that all items in #1 are also in #2 and they are identical
246             for (ITestDevice.ApexInfo ai : fromApexd) {
247                 ApexInfo apexFromXml = fromXml.get(ai.sourceDir);
248                 assertWithMessage("APEX (" + ai.toString() + ") is not found in the list")
249                         .that(apexFromXml).isNotNull();
250                 assertWithMessage("Version mismatch for APEX (" + ai.toString() + ")")
251                         .that(ai.versionCode).isEqualTo(apexFromXml.getVersionCode());
252                 assertWithMessage("APEX (" + ai.toString() + ") is not active")
253                         .that(apexFromXml.getIsActive()).isTrue();
254             }
255         }
256     }
257 
258     /**
259      * Test to verify that the state of a staged session does not change if apexd is stopped and
260      * restarted while a session is staged.
261      */
262     @Test
testApexSessionStateUnchangedBeforeReboot()263     public void testApexSessionStateUnchangedBeforeReboot() throws Exception {
264         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
265         assumeTrue("Device requires root", getDevice().isAdbRoot());
266 
267         File apexFile = mHostUtils.getTestFile("com.android.apex.cts.shim.v2.apex");
268         String error = mHostUtils.installStagedPackage(apexFile);
269         assertThat(error).isNull();
270         String sessionId = getDevice().executeShellCommand(
271                 "pm get-stagedsessions --only-ready --only-parent --only-sessionid").trim();
272         assertThat(sessionId).isNotEmpty();
273         String sessionStateCmd = "cmd -w apexservice getStagedSessionInfo " + sessionId;
274         String initialState = getDevice().executeShellV2Command(sessionStateCmd).getStdout();
275         assertThat(initialState).isNotEmpty();
276 
277         // Kill apexd. This means apexd will perform its start logic when the second install
278         // is staged.
279         getDevice().executeShellV2Command("kill `pidof apexd`");
280 
281         // Verify that the session state remains consistent after apexd has restarted.
282         String updatedState = getDevice().executeShellV2Command(sessionStateCmd).getStdout();
283         assertThat(updatedState).isEqualTo(initialState);
284     }
285 
286     /**
287      * Verifies that content of {@code /data/apex/sessions/} is migrated to the {@code
288      * /metadata/apex/sessions}.
289      */
290     @Test
testSessionsDirMigrationToMetadata()291     public void testSessionsDirMigrationToMetadata() throws Exception {
292         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
293         assumeTrue("Device requires root", getDevice().isAdbRoot());
294 
295         try {
296             getDevice().executeShellV2Command("mkdir -p /data/apex/sessions/1543");
297             File file = File.createTempFile("foo", "bar");
298             getDevice().pushFile(file, "/data/apex/sessions/1543/file");
299 
300             // During boot sequence apexd will move /data/apex/sessions/1543/file to
301             // /metadata/apex/sessions/1543/file.
302             getDevice().reboot();
303             assertWithMessage("Timed out waiting for device to boot").that(
304                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
305 
306             assertThat(getDevice().doesFileExist("/metadata/apex/sessions/1543/file")).isTrue();
307             assertThat(getDevice().doesFileExist("/data/apex/sessions/1543/file")).isFalse();
308         } finally {
309             getDevice().executeShellV2Command("rm -R /data/apex/sessions/1543");
310             getDevice().executeShellV2Command("rm -R /metadata/apex/sessions/1543");
311         }
312     }
313 
314     @Test
testFailsToActivateApexOnDataFallbacksToPreInstalled()315     public void testFailsToActivateApexOnDataFallbacksToPreInstalled() throws Exception {
316         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
317         assumeTrue("Device requires root", getDevice().isAdbRoot());
318 
319         try {
320             final File file =
321                     mHostUtils.getTestFile("com.android.apex.cts.shim.v2_additional_file.apex");
322             getDevice().pushFile(file, "/data/apex/active/com.android.apex.cts.shim@2.apex");
323 
324             getDevice().reboot();
325             assertWithMessage("Timed out waiting for device to boot").that(
326                     getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
327 
328             // After reboot pre-installed version of shim apex should be activated, and corrupted
329             // version on /data should be deleted.
330             final Set<ITestDevice.ApexInfo> activeApexes = getDevice().getActiveApexes();
331             ITestDevice.ApexInfo testApex = new ITestDevice.ApexInfo(
332                     "com.android.apex.cts.shim", 1L);
333             assertThat(activeApexes).contains(testApex);
334             assertThat(
335                     getDevice()
336                             .doesFileExist("/data/apex/active/com.android.apex.cts.shim@2.apex"))
337                     .isFalse();
338         } finally {
339             getDevice().deleteFile("/data/apex/active/com.android.apex.cts.shim@2.apex");
340         }
341     }
342 }
343