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.modules.testing.utils;
18 
19 import static org.junit.Assert.assertTrue;
20 
21 import android.cts.install.lib.host.InstallUtilsHost;
22 
23 import com.android.tradefed.device.ITestDevice;
24 import com.android.tradefed.invoker.TestInformation;
25 
26 import com.android.internal.util.test.SystemPreparer;
27 import com.android.internal.util.test.SystemPreparer.DeviceProvider;
28 import com.android.internal.util.test.SystemPreparer.RebootStrategy;
29 
30 import org.junit.rules.TemporaryFolder;
31 
32 import java.io.File;
33 
34 /**
35  * Utility to help installing and updating apexes in tests as well as cleaning them up when the test
36  * finishes.
37  *
38  * <p>Install vs updating: you can update any apex preinstalled in the system image as long as your
39  * apex has a higher version than the one currently installed using
40  * {@link #installApexAndReboot}. If you want to use an apex not preinstalled you can call
41  * {@link #pushApexAndReboot}. After you push one (non-preinstalled) apex, you can update it using
42  * either method.
43  *
44  * <p>In order to revert the apexes that were installed or pushed, call
45  * {@link #revertChanges}. This normally happens from your {@code @AfterClass} method.
46  *
47  * <p>Requirements: the device must support apex updates and have root access via adb.
48  *
49  * <p>Example usage:
50  *
51  * <pre>{@code
52  *
53  *     static private ApexInstallHelper sApexInstallHelper;
54  *
55  *     @BeforeClassWithInfo
56  *     public static void beforeClassWithDevice(TestInformation testInformation)
57  *             throws Exception {
58  *          sApexInstallHelper = new ApexInstallHelper(testInformation);
59  *          sApexInstallHelper.pushApexAndReboot("test_com.android.modules.example.apex");
60  *          // assuming next line matches a preinstalled apex
61  *          sApexInstallHelper.installApexAndReboot("art.apex");
62  *     }
63  *
64  *      @AfterClass
65  *      public static void afterClass() {
66  *          sApexInstallHelper.revertChanges();
67  *      }
68  *
69  *      // add your own @Test methods
70  * }</pre>
71  *
72  * <p>Additionally, the apex files that you want to update or install need to be accessible
73  * to the test:
74  *
75  * <pre>{@code
76  *
77  * java_test_host {
78  *     name: "Example",
79  *     data: [
80  *         ":test_com.android.modules.example",
81  *     ],
82  *     static_libs: [
83  *         "com.android.modules.testing.utils",
84  *     ],
85  * }</pre>
86  *
87  */
88 public class ApexInstallHelper {
89     private final TemporaryFolder mTemporaryFolder;
90     private final SystemPreparer mSystemPreparer;
91     private final InstallUtilsHost mInstallUtilsHost;
92     private final DeviceProvider mDeviceProvider;
93     private boolean mCalledSetUp = false;
94 
ApexInstallHelper(TestInformation testInformation)95     public ApexInstallHelper(TestInformation testInformation) {
96         this(testInformation, RebootStrategy.FULL);
97     }
98 
ApexInstallHelper(TestInformation testInformation, RebootStrategy rebootStrategy)99     public ApexInstallHelper(TestInformation testInformation, RebootStrategy rebootStrategy) {
100         mDeviceProvider = testInformation::getDevice;
101         mTemporaryFolder = new TemporaryFolder();
102         mSystemPreparer = new SystemPreparer(mTemporaryFolder, rebootStrategy,
103                 /*testRuleDelegate=*/null, mDeviceProvider);
104         mInstallUtilsHost = new InstallUtilsHost(testInformation);
105     }
106 
setUp()107     private void setUp() throws Exception {
108         if (mCalledSetUp) {
109             return;
110         }
111         mCalledSetUp = true;
112         mTemporaryFolder.create();
113         // mSystemPreparer doesn't expect to be called to setUp
114 
115         ITestDevice device = mDeviceProvider.getDevice();
116         if (!device.isAdbRoot()) {
117             device.enableAdbRoot();
118         }
119 
120         assertTrue("Device does not support updating APEX",
121                 mInstallUtilsHost.isApexUpdateSupported());
122         assertTrue("Device requires root", device.isAdbRoot());
123     }
124 
125     /**
126      * Installs an apex (using {@code adb push}) on the device. This can be used to install a new
127      * apex that is not preinstalled in the system image.
128      *
129      * @param apexFilename the filename of the apex to install.
130      */
pushApexAndReboot(String apexFilename)131     public void pushApexAndReboot(String apexFilename) throws Exception {
132         setUp();
133         File apexFile = mInstallUtilsHost.getTestFile(apexFilename);
134         mSystemPreparer.pushFile(apexFile, "/system/apex/" + apexFilename);
135         mSystemPreparer.reboot();
136     }
137 
138     /**
139      * Installs an apex (using {@code adb install}) on the device. This can only be used to
140      * install a newer version of an apex that is already installed on the device (either
141      * preinstalled in the system image or previously installed using
142      * {@link #installApexAndReboot}).
143      *
144      * @param apexFilename the filename of the apex which contains the update.
145      */
installApexAndReboot(String apexFilename)146     public void installApexAndReboot(String apexFilename) throws Exception {
147         setUp();
148         File apexFile = mInstallUtilsHost.getTestFile(apexFilename);
149         mDeviceProvider.getDevice().installPackage(apexFile, true);
150         mSystemPreparer.reboot();
151     }
152 
153     /**
154      * Calls this method to undo all installs and pushes performed by this helper when your test
155      * has finished.
156      *
157      * <p>This should typically be called from your {@code @AfterClass} method.
158      */
revertChanges()159     public void revertChanges() {
160         if (!mCalledSetUp) {
161             return;
162         }
163         // in line with other tests, clean up system preparer before the temporary folder
164         mSystemPreparer.after();
165         mTemporaryFolder.delete();
166         mCalledSetUp = false;
167     }
168 }
169