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.apex.host;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assume.assumeTrue;
23 
24 import android.cts.install.lib.host.InstallUtilsHost;
25 import android.platform.test.annotations.LargeTest;
26 
27 import com.android.apex.ApexInfo;
28 import com.android.apex.XmlParser;
29 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
30 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
31 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
32 import com.android.tradefed.util.CommandResult;
33 import com.android.tradefed.util.CommandStatus;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import static java.util.stream.Collectors.toList;
41 
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.nio.file.Paths;
45 import java.util.List;
46 
47 @RunWith(DeviceJUnit4ClassRunner.class)
48 public class VendorApexTests extends BaseHostJUnit4Test {
49 
50     private static final String TAG = "VendorApexTests";
51     private static final String APEX_PACKAGE_NAME = "com.android.apex.vendor.foo";
52 
53     private final InstallUtilsHost mHostUtils = new InstallUtilsHost(this);
54 
runPhase(String phase)55     private void runPhase(String phase) throws Exception {
56         assertThat(runDeviceTests("com.android.tests.vendorapex.app",
57                 "com.android.tests.apex.app.VendorApexTests",
58                 phase)).isTrue();
59     }
60 
61     @Before
setUp()62     public void setUp() throws Exception {
63         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
64 
65         // TODO(b/280155297) workaround to ensure RW. When partition.vendor.verified is still
66         // set to something, then the device should be remount-ed again.
67         getDevice().remountVendorWritable();
68         String verity = getDevice().getProperty("partition.vendor.verified");
69         if (verity != null && !verity.isEmpty()) {
70             getDevice().remountVendorWritable();
71         }
72     }
73 
74     @After
tearDown()75     public void tearDown() throws Exception {
76         deleteFiles("/vendor/apex/" + APEX_PACKAGE_NAME + "*apex",
77                 "/data/apex/active/" + APEX_PACKAGE_NAME + "*apex");
78     }
79 
80     @Test
81     @LargeTest
testRebootlessUpdate()82     public void testRebootlessUpdate() throws Exception {
83         pushPreinstalledApex("com.android.apex.vendor.foo.apex");
84 
85         runPhase("testRebootlessUpdate");
86     }
87 
88     @Test
89     @LargeTest
testGenerateLinkerConfigurationOnUpdate()90     public void testGenerateLinkerConfigurationOnUpdate() throws Exception {
91         pushPreinstalledApex("com.android.apex.vendor.foo.apex");
92         runPhase("testGenerateLinkerConfigurationOnUpdate");
93     }
94 
95     @Test
96     @LargeTest
testInstallAbortsWhenVndkVersionMismatches()97     public void testInstallAbortsWhenVndkVersionMismatches() throws Exception {
98         pushPreinstalledApex("com.android.apex.vendor.foo.apex");
99         runPhase("testInstallAbortsWhenVndkVersionMismatches");
100         runPhase("testInstallAbortsWhenVndkVersionMismatches_Staged");
101     }
102 
103     @Test
104     @LargeTest
testApexAllReady()105     public void testApexAllReady() throws Exception {
106         pushPreinstalledApex("com.android.apex.vendor.foo.apex.all.ready.apex");
107         assertThat(getDevice().getProperty("vendor.test.apex.all.ready")).isEqualTo("triggered");
108     }
109 
110     @Test
111     @LargeTest
testRestartServiceAfterRebootlessUpdate()112     public void testRestartServiceAfterRebootlessUpdate() throws Exception {
113         pushPreinstalledApex("com.android.apex.vendor.foo.v1_with_service.apex");
114         runPhase("testRestartServiceAfterRebootlessUpdate");
115     }
116 
117     @Test
118     @LargeTest
testVendorBootstrapApex()119     public void testVendorBootstrapApex() throws Exception {
120         pushPreinstalledApex("com.android.apex.vendor.foo.bootstrap.apex");
121 
122         // Now there should be "com.android.apex.vendor.foo" activated as an
123         // bootstrap apex, listed in apex-info-list in the bootstrap mount namespace.
124         try (FileInputStream fis = new FileInputStream(
125                 getDevice().pullFile("/bootstrap-apex/apex-info-list.xml"))) {
126             List<String> names = XmlParser.readApexInfoList(fis)
127                     .getApexInfo()
128                     .stream()
129                     .map(ApexInfo::getModuleName)
130                     .collect(toList());
131             assertThat(names).contains("com.android.apex.vendor.foo");
132         }
133 
134         // And also the `early_hal` service in the apex (apex_vendor_foo) should
135         // be started in the bootstrap mount namespace.
136         assertThat(getMountNamespaceFor("$(pidof apex_vendor_foo)"))
137             .isEqualTo(getMountNamespaceFor("$(pidof vold)"));
138     }
139 
getMountNamespaceFor(String proc)140     private String getMountNamespaceFor(String proc) throws Exception {
141         CommandResult result =
142                 getDevice().executeShellV2Command("readlink /proc/" + proc + "/ns/mnt");
143         if (result.getStatus() != CommandStatus.SUCCESS) {
144             throw new RuntimeException("failed to read namespace for " + proc);
145         }
146         return result.getStdout().trim();
147     }
148 
pushPreinstalledApex(String fileName)149     private void pushPreinstalledApex(String fileName) throws Exception {
150         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
151         final File apex = buildHelper.getTestFile(fileName);
152         assertTrue(getDevice().pushFile(apex, Paths.get("/vendor/apex", fileName).toString()));
153         getDevice().reboot();
154     }
155 
156     /**
157      * Deletes files and reboots the device if necessary.
158      * @param files the paths of files which might contain wildcards
159      */
deleteFiles(String... files)160     private void deleteFiles(String... files) throws Exception {
161         boolean found = false;
162         for (String file : files) {
163             CommandResult result = getDevice().executeShellV2Command("ls " + file);
164             if (result.getStatus() == CommandStatus.SUCCESS) {
165                 found = true;
166                 break;
167             }
168         }
169 
170         if (found) {
171             getDevice().remountVendorWritable();
172             for (String file : files) {
173                 getDevice().executeShellCommand("rm -rf " + file);
174             }
175             getDevice().reboot();
176         }
177     }
178 }
179