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.tradefed.targetprep;
18 
19 import static org.junit.Assert.assertArrayEquals;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertThrows;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyString;
26 import static org.mockito.ArgumentMatchers.eq;
27 import static org.mockito.Mockito.atLeastOnce;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.times;
30 import static org.mockito.Mockito.verify;
31 import static org.mockito.Mockito.when;
32 
33 import com.android.tradefed.device.DeviceNotAvailableException;
34 import com.android.tradefed.device.ITestDevice;
35 import com.android.tradefed.util.CommandResult;
36 import com.android.tradefed.util.CommandStatus;
37 
38 import com.google.common.collect.ImmutableMap;
39 import com.google.common.collect.ImmutableMultimap;
40 
41 import org.junit.Before;
42 import org.junit.Rule;
43 import org.junit.Test;
44 import org.junit.rules.TemporaryFolder;
45 import org.junit.runner.RunWith;
46 import org.junit.runners.JUnit4;
47 import org.mockito.Mock;
48 import org.mockito.MockitoAnnotations;
49 
50 import java.io.File;
51 import java.nio.file.Files;
52 import java.nio.file.Path;
53 import java.nio.file.Paths;
54 import java.util.Collections;
55 import java.util.HashSet;
56 import java.util.Map;
57 
58 /** Unit test for {@link ModulePusher} */
59 @RunWith(JUnit4.class)
60 public final class ModulePusherTest {
61     public static final String TESTHARNESS_ENABLE = "cmd testharness enable";
62     private static final String APEX_PACKAGE_NAME = "com.android.FAKE.APEX.PACKAGE.NAME";
63     private static final String APK_PACKAGE_NAME = "com.android.FAKE.APK.PACKAGE.NAME";
64     private static final String SPLIT_APK_PACKAGE_NAME = "com.android.SPLIT.FAKE.APK.PACKAGE.NAME";
65     private static final String APEX_PRELOAD_NAME = APEX_PACKAGE_NAME + ".apex";
66     private static final String APK_PRELOAD_NAME = APK_PACKAGE_NAME + ".apk";
67     private static final String SPLIT_APK_PRELOAD_NAME = SPLIT_APK_PACKAGE_NAME + ".apk";
68     private static final String APEX_PATH_ON_DEVICE = "/system/apex/" + APEX_PRELOAD_NAME;
69     private static final String APK_PATH_ON_DEVICE = "/system/apps/" + APK_PRELOAD_NAME;
70     public static final String SPLIT_APK_PACKAGE_ON_DEVICE =
71             "/system/apps/com.android.SPLIT.FAKE.APK.PACKAGE.NAME";
72     private static final String SPLIT_APK_PATH_ON_DEVICE =
73             SPLIT_APK_PACKAGE_ON_DEVICE + "/" + SPLIT_APK_PRELOAD_NAME;
74     private static final String HDPI_PATH_ON_DEVICE =
75             SPLIT_APK_PACKAGE_ON_DEVICE + "/com.android.SPLIT.FAKE.APK.PACKAGE.NAME-hdpi.apk";
76     private static final String TEST_APEX_NAME = "fakeApex.apex";
77     private static final String TEST_APK_NAME = "fakeApk.apk";
78     private static final String TEST_SPLIT_APK_NAME = "FakeSplit/base-master.apk";
79     private static final String TEST_HDPI_APK_NAME = "FakeSplit/base-hdpi.apk";
80 
81     @Rule public TemporaryFolder testDir = new TemporaryFolder();
82     private static final String SERIAL = "serial";
83     private static final int API = 30;
84     private ModulePusher mPusher;
85     @Mock ITestDevice mMockDevice;
86     private File mFakeApex;
87     private File mFakeApk;
88     private File mFakeSplitDir;
89     private File mFakeSplitApk;
90     private File mFakeHdpiApk;
91 
92     @Before
setUp()93     public void setUp() throws Exception {
94         MockitoAnnotations.initMocks(this);
95 
96         mFakeApex = testDir.newFile(TEST_APEX_NAME);
97         mFakeApk = testDir.newFile(TEST_APK_NAME);
98         mFakeSplitDir = testDir.newFolder("FakeSplit");
99         mFakeSplitApk = testDir.newFile(TEST_SPLIT_APK_NAME);
100         mFakeHdpiApk = testDir.newFile(TEST_HDPI_APK_NAME);
101 
102         when(mMockDevice.executeAdbCommand("disable-verity")).thenReturn("disabled");
103         when(mMockDevice.executeAdbCommand("remount")).thenReturn("remount succeeded");
104         when(mMockDevice.getApiLevel()).thenReturn(API);
105         when(mMockDevice.getSerialNumber()).thenReturn(SERIAL);
106         when(mMockDevice.getDeviceDescriptor()).thenReturn(null);
107         CommandResult apexCr = getCommandResult(APEX_PRELOAD_NAME + "\n");
108         when(mMockDevice.executeShellV2Command("ls /system/apex/")).thenReturn(apexCr);
109         CommandResult cr = getCommandResult("Good!");
110         when(mMockDevice.executeShellV2Command("pm get-moduleinfo | grep 'com.google'"))
111                 .thenReturn(cr);
112         when(mMockDevice.executeShellV2Command("cmd testharness enable")).thenReturn(cr);
113         CommandResult cr1 =
114                 getCommandResult("package:/system/apex/com.android.FAKE.APEX.PACKAGE.NAME.apex\n");
115         when(mMockDevice.executeShellV2Command("pm path " + APEX_PACKAGE_NAME)).thenReturn(cr1);
116         CommandResult cr2 =
117                 getCommandResult("package:/system/apps/com.android.FAKE.APK.PACKAGE.NAME.apk\n");
118         when(mMockDevice.executeShellV2Command("pm path " + APK_PACKAGE_NAME)).thenReturn(cr2);
119         CommandResult cr3 =
120                 getCommandResult(
121                         String.format(
122                                 "package:%s\npackage:%s\n",
123                                 SPLIT_APK_PATH_ON_DEVICE, HDPI_PATH_ON_DEVICE));
124         when(mMockDevice.executeShellV2Command("pm path " + SPLIT_APK_PACKAGE_NAME))
125                 .thenReturn(cr3);
126         CommandResult cr4 =
127                 getCommandResult(
128                         "com.android.SPLIT.FAKE.APK.PACKAGE.NAME.apk\n"
129                                 + "com.android.SPLIT.FAKE.APK.PACKAGE.NAME-hdpi.apk\n");
130         when(mMockDevice.executeShellV2Command(
131                         "ls /system/apps/com.android.SPLIT.FAKE.APK.PACKAGE.NAME"))
132                 .thenReturn(cr4);
133 
134         mPusher =
135                 new ModulePusher(mMockDevice, 0, 0) {
136                     @Override
137                     protected ModulePusher.ModuleInfo retrieveModuleInfo(File packageFile) {
138                         if (mFakeApex.equals(packageFile)) {
139                             return ModuleInfo.create(APEX_PACKAGE_NAME, "2", false);
140                         } else if (mFakeApk.equals(packageFile)) {
141                             return ModuleInfo.create(APK_PACKAGE_NAME, "2", true);
142                         } else {
143                             return ModuleInfo.create(SPLIT_APK_PACKAGE_NAME, "2", true);
144                         }
145                     }
146 
147                     @Override
148                     protected void waitForDeviceToBeResponsive(long waitTime) {}
149                 };
150     }
151 
152     /** Test parsePackageVersionCodes get the right version codes. */
153     @Test
testParsePackageVersionCodes()154     public void testParsePackageVersionCodes() {
155         String outputs =
156                 "package:com.google.android.media versionCode:301800200\n"
157                         + "package:com.google.android.mediaprovider versionCode:301501700\n"
158                         + "package: com.google.wrong.pattern version:\n"
159                         + "package:com.google.empty versionCode:\n"
160                         + "package:com.google.android.media.swcodec versionCode:301700000";
161 
162         Map<String, String> versionCodes = mPusher.parsePackageVersionCodes(outputs);
163 
164         assertEquals(3, versionCodes.size());
165         assertEquals("301800200", versionCodes.get("com.google.android.media"));
166         assertEquals("301501700", versionCodes.get("com.google.android.mediaprovider"));
167         assertEquals("301700000", versionCodes.get("com.google.android.media.swcodec"));
168     }
169 
170     /** Test getting paths on device. */
171     @Test
testGetPathsOnDevice()172     public void testGetPathsOnDevice() throws Exception {
173         String[] files = mPusher.getPathsOnDevice(mMockDevice, SPLIT_APK_PACKAGE_NAME);
174 
175         assertArrayEquals(new String[] {SPLIT_APK_PATH_ON_DEVICE, HDPI_PATH_ON_DEVICE}, files);
176     }
177 
178     @Test
testGetApexPathUnderSystem()179     public void testGetApexPathUnderSystem() throws Exception {
180         CommandResult apexCr =
181                 getCommandResult(
182                         "com.android.apex.cts.shim.apex\n"
183                                 + "com.android.wifi.capex\n"
184                                 + "com.android.appsearch.apex\n"
185                                 + "com.google.android.adbd_trimmed_compressed.apex\n"
186                                 + "com.google.android.art_compressed.apex\n"
187                                 + "com.google.android.media.swcodec_compressed.apex\n"
188                                 + "com.google.android.media_compressed.apex\n"
189                                 + "com.google.android.mediaprovider_compressed.apex\n"
190                                 + "com.google.mainline.primary.libs.apex");
191         when(mMockDevice.executeShellV2Command("ls /system/apex/")).thenReturn(apexCr);
192 
193         assertEquals(
194                 Paths.get("/system/apex/com.android.appsearch.apex"),
195                 mPusher.getApexPathUnderSystem(mMockDevice, "com.android.appsearch"));
196         assertEquals(
197                 Paths.get("/system/apex/com.google.android.media_compressed.apex"),
198                 mPusher.getApexPathUnderSystem(mMockDevice, "com.google.android.media"));
199         assertEquals(
200                 Paths.get("/system/apex/com.google.android.adbd_trimmed_compressed.apex"),
201                 mPusher.getApexPathUnderSystem(mMockDevice, "com.google.android.adbd"));
202     }
203 
204     /** Test getting preload paths for split apks. */
205     @Test
testGetPreLoadPathsOnSplitApk()206     public void testGetPreLoadPathsOnSplitApk() throws Exception {
207         File[] files = new File[] {mFakeSplitApk, mFakeHdpiApk};
208         Path[] actual =
209                 new Path[] {
210                     Paths.get(SPLIT_APK_PACKAGE_ON_DEVICE),
211                     Paths.get(SPLIT_APK_PATH_ON_DEVICE),
212                     Paths.get(HDPI_PATH_ON_DEVICE)
213                 };
214 
215         Path[] results = mPusher.getPreloadPaths(mMockDevice, files, SPLIT_APK_PACKAGE_NAME, 30);
216 
217         assertArrayEquals(results, actual);
218     }
219 
220     /** Test getting preload paths for apex */
221     @Test
testGetPreLoadPathsOnApex()222     public void testGetPreLoadPathsOnApex() throws Exception {
223         File[] files = new File[] {mFakeApex};
224         Path[] actual = new Path[] {Paths.get(APEX_PATH_ON_DEVICE)};
225 
226         Path[] results = mPusher.getPreloadPaths(mMockDevice, files, APEX_PACKAGE_NAME, 30);
227 
228         assertArrayEquals(results, actual);
229     }
230 
231     /** Test getting default path if `pm path` fails on Q */
232     @Test
testGetPreLoadPathsOnQReturnDefault()233     public void testGetPreLoadPathsOnQReturnDefault() throws Exception {
234         File[] files = new File[] {mFakeApex};
235         Path[] actual = new Path[] {Paths.get(APEX_PATH_ON_DEVICE)};
236         when(mMockDevice.executeShellV2Command("pm path " + APEX_PACKAGE_NAME))
237                 .thenReturn(getCommandResult(""));
238 
239         Path[] results = mPusher.getPreloadPaths(mMockDevice, files, APEX_PACKAGE_NAME, 29);
240 
241         assertArrayEquals(results, actual);
242     }
243 
244     /** Test get preload paths throws an exception if `pm path` fails on S */
245     @Test
testGetPreLoadPathsOnSThrowsException()246     public void testGetPreLoadPathsOnSThrowsException() throws Exception {
247         File[] files = new File[] {mFakeApex};
248         when(mMockDevice.executeShellV2Command("pm path " + APEX_PACKAGE_NAME))
249                 .thenReturn(getCommandResult(""));
250 
251         assertThrows(
252                 ModulePusher.ModulePushError.class,
253                 () -> mPusher.getPreloadPaths(mMockDevice, files, APEX_PACKAGE_NAME, 31));
254     }
255 
256     /** Test getting preload paths for non-split apk. */
257     @Test
testGetPreLoadPathsOnApk()258     public void testGetPreLoadPathsOnApk() throws Exception {
259         File[] files = new File[] {mFakeApk};
260         Path[] actual = new Path[] {Paths.get(APK_PATH_ON_DEVICE)};
261 
262         Path[] results = mPusher.getPreloadPaths(mMockDevice, files, APK_PACKAGE_NAME, 30);
263 
264         assertArrayEquals(results, actual);
265     }
266 
267     /** Test getting preload paths for /data/apex/decompressed */
268     @Test
testGetPreLoadPathsOverridesApexDecompressedPath()269     public void testGetPreLoadPathsOverridesApexDecompressedPath() throws Exception {
270         File[] files = new File[] {mFakeApex};
271         when(mMockDevice.executeShellV2Command("pm path " + APEX_PACKAGE_NAME))
272                 .thenReturn(
273                         getCommandResult(
274                                 "package:/data/apex/decompressed/"
275                                         + APEX_PACKAGE_NAME
276                                         + "@310000000.decompressed.apex\n"));
277         Path[] actual = new Path[] {Paths.get(APEX_PATH_ON_DEVICE)};
278 
279         Path[] results = mPusher.getPreloadPaths(mMockDevice, files, APEX_PACKAGE_NAME, 31);
280 
281         assertArrayEquals(results, actual);
282     }
283 
284     /** Test install modules when there are non-split files to push. */
285     @Test
testInstallModulesSuccess()286     public void testInstallModulesSuccess() throws Exception {
287         Path dir = mFakeApex.toPath().getParent();
288         File renamedApex = dir.resolve(APEX_PRELOAD_NAME).toFile();
289         File renamedApk = dir.resolve(APK_PRELOAD_NAME).toFile();
290         when(mMockDevice.pushFile(renamedApex, APEX_PATH_ON_DEVICE)).thenReturn(true);
291         when(mMockDevice.pushFile(renamedApk, APK_PATH_ON_DEVICE)).thenReturn(true);
292         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2", APK_PACKAGE_NAME, "2"));
293         activateVersion(2);
294 
295         mPusher.installModules(
296                 ImmutableMultimap.of(APEX_PACKAGE_NAME, mFakeApex, APK_PACKAGE_NAME, mFakeApk),
297                 /*factoryReset=*/ true,
298                 /*disablePackageCache=*/ false);
299 
300         verify(mMockDevice, atLeastOnce()).getActiveApexes();
301         verify(mMockDevice, times(2)).reboot();
302         verify(mMockDevice, times(1)).executeShellV2Command("cmd testharness enable");
303         verify(mMockDevice, times(1)).pushFile(renamedApex, APEX_PATH_ON_DEVICE);
304         verify(mMockDevice, times(1)).pushFile(renamedApk, APK_PATH_ON_DEVICE);
305         assertFalse(Files.exists(mFakeApex.toPath()));
306         assertFalse(Files.exists(mFakeApk.toPath()));
307         assertTrue(Files.isRegularFile(renamedApex.toPath()));
308         assertTrue(Files.isRegularFile(renamedApk.toPath()));
309     }
310 
311     /** Test install modules when there are non-split files to push with reboot. */
312     @Test
testInstallModulesSuccessViaReboot()313     public void testInstallModulesSuccessViaReboot() throws Exception {
314         Path dir = mFakeApex.toPath().getParent();
315         File renamedApex = dir.resolve(APEX_PRELOAD_NAME).toFile();
316         File renamedApk = dir.resolve(APK_PRELOAD_NAME).toFile();
317         when(mMockDevice.pushFile(renamedApex, APEX_PATH_ON_DEVICE)).thenReturn(true);
318         when(mMockDevice.pushFile(renamedApk, APK_PATH_ON_DEVICE)).thenReturn(true);
319         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2", APK_PACKAGE_NAME, "2"));
320         activateVersion(2);
321 
322         mPusher.installModules(
323                 ImmutableMultimap.of(APEX_PACKAGE_NAME, mFakeApex, APK_PACKAGE_NAME, mFakeApk),
324                 /*factoryReset=*/ false,
325                 /*disablePackageCache=*/ false);
326 
327         verify(mMockDevice, atLeastOnce()).getActiveApexes();
328         verify(mMockDevice, never()).executeShellV2Command("cmd testharness enable");
329         verify(mMockDevice, times(3)).reboot();
330         verify(mMockDevice, times(1)).pushFile(renamedApex, APEX_PATH_ON_DEVICE);
331         verify(mMockDevice, times(1)).pushFile(renamedApk, APK_PATH_ON_DEVICE);
332         assertFalse(Files.exists(mFakeApex.toPath()));
333         assertFalse(Files.exists(mFakeApk.toPath()));
334         assertTrue(Files.isRegularFile(renamedApex.toPath()));
335         assertTrue(Files.isRegularFile(renamedApk.toPath()));
336     }
337 
338     /** Test install modules when there are apks to push. */
339     @Test
testInstallModulesSuccessWithApks()340     public void testInstallModulesSuccessWithApks() throws Exception {
341         Path dir = mFakeApex.toPath().getParent();
342         File renamedApex = dir.resolve(APEX_PRELOAD_NAME).toFile();
343         File renamedSplitApk = dir.resolve(SPLIT_APK_PACKAGE_NAME).toFile();
344         when(mMockDevice.pushFile(renamedApex, APEX_PATH_ON_DEVICE)).thenReturn(true);
345         when(mMockDevice.pushDir(renamedSplitApk, SPLIT_APK_PACKAGE_ON_DEVICE)).thenReturn(true);
346         setVersionCodesOnDevice(
347                 ImmutableMap.of(APEX_PACKAGE_NAME, "2", SPLIT_APK_PACKAGE_NAME, "2"));
348         activateVersion(2);
349 
350         mPusher.installModules(
351                 ImmutableMultimap.of(
352                         APEX_PACKAGE_NAME,
353                         mFakeApex,
354                         SPLIT_APK_PACKAGE_NAME,
355                         mFakeSplitApk,
356                         SPLIT_APK_PACKAGE_NAME,
357                         mFakeHdpiApk),
358                 /*factoryReset=*/ true,
359                 /*disablePackageCache=*/ false);
360 
361         verify(mMockDevice, atLeastOnce()).getActiveApexes();
362         verify(mMockDevice, times(1)).executeShellV2Command("cmd testharness enable");
363         verify(mMockDevice, times(2)).reboot();
364         verify(mMockDevice, times(1)).pushFile(renamedApex, APEX_PATH_ON_DEVICE);
365         verify(mMockDevice, times(1)).pushDir(renamedSplitApk, SPLIT_APK_PACKAGE_ON_DEVICE);
366         assertFalse(Files.exists(mFakeApex.toPath()));
367         assertFalse(Files.exists(mFakeSplitDir.toPath()));
368         assertTrue(Files.isRegularFile(renamedApex.toPath()));
369         assertTrue(Files.isDirectory(renamedSplitApk.toPath()));
370         assertTrue(Files.isRegularFile(renamedSplitApk.toPath().resolve("base-master.apk")));
371         assertTrue(Files.isRegularFile(renamedSplitApk.toPath().resolve("base-hdpi.apk")));
372     }
373 
374     /** Test install modules when disable package cache. */
375     @Test
testInstallModulesSuccessDisablePackageCache()376     public void testInstallModulesSuccessDisablePackageCache() throws Exception {
377         Path dir = mFakeApex.toPath().getParent();
378         File renamedApex = dir.resolve(APEX_PRELOAD_NAME).toFile();
379         File renamedApk = dir.resolve(APK_PRELOAD_NAME).toFile();
380         when(mMockDevice.pushFile(renamedApex, APEX_PATH_ON_DEVICE)).thenReturn(true);
381         when(mMockDevice.pushFile(renamedApk, APK_PATH_ON_DEVICE)).thenReturn(true);
382         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2", APK_PACKAGE_NAME, "2"));
383         activateVersion(2);
384 
385         mPusher.installModules(
386                 ImmutableMultimap.of(APEX_PACKAGE_NAME, mFakeApex, APK_PACKAGE_NAME, mFakeApk),
387                 /*factoryReset=*/ false,
388                 /*disablePackageCache=*/ true);
389 
390         verify(mMockDevice, atLeastOnce()).getActiveApexes();
391         verify(mMockDevice, never()).executeShellV2Command("cmd testharness enable");
392         verify(mMockDevice, times(1)).executeShellV2Command("rm -Rf /data/system/package_cache/");
393         verify(mMockDevice, times(3)).reboot();
394         verify(mMockDevice, times(1)).pushFile(renamedApex, APEX_PATH_ON_DEVICE);
395         verify(mMockDevice, times(1)).pushFile(renamedApk, APK_PATH_ON_DEVICE);
396         assertFalse(Files.exists(mFakeApex.toPath()));
397         assertFalse(Files.exists(mFakeApk.toPath()));
398         assertTrue(Files.isRegularFile(renamedApex.toPath()));
399         assertTrue(Files.isRegularFile(renamedApk.toPath()));
400     }
401 
402     /** Throws exception when missing one version code. */
403     @Test
testInstallModulesThrowsExceptionWhenMissingVersionCode()404     public void testInstallModulesThrowsExceptionWhenMissingVersionCode() throws Exception {
405         Path dir = mFakeApex.toPath().getParent();
406         File renamedApex = dir.resolve(APEX_PRELOAD_NAME).toFile();
407         File renamedApk = dir.resolve(APK_PRELOAD_NAME).toFile();
408         when(mMockDevice.pushFile(renamedApex, APEX_PATH_ON_DEVICE)).thenReturn(true);
409         when(mMockDevice.pushFile(renamedApk, APK_PATH_ON_DEVICE)).thenReturn(true);
410         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2"));
411         activateVersion(2);
412 
413         assertThrows(
414                 ModulePusher.ModulePushError.class,
415                 () ->
416                         mPusher.installModules(
417                                 ImmutableMultimap.of(
418                                         APEX_PACKAGE_NAME, mFakeApex, APK_PACKAGE_NAME, mFakeApk),
419                                 /*factoryReset=*/ false,
420                                 /*disablePackageCache=*/ false));
421         verify(mMockDevice, atLeastOnce()).getActiveApexes();
422         verify(mMockDevice, never()).executeShellV2Command("cmd testharness enable");
423         verify(mMockDevice, times(2)).reboot();
424         verify(mMockDevice, never()).pushFile(any(File.class), anyString());
425     }
426 
427     /** Throws ModulePushError if the only file push fails. */
428     @Test
testInstallModulesFailureIfPushFails()429     public void testInstallModulesFailureIfPushFails() throws Exception {
430         Path dir = mFakeApex.toPath().getParent();
431         File renamedApex = dir.resolve(APEX_PRELOAD_NAME).toFile();
432         when(mMockDevice.pushFile(any(), eq(APEX_PATH_ON_DEVICE))).thenReturn(false);
433         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2"));
434 
435         assertThrows(
436                 ModulePusher.ModulePushError.class,
437                 () ->
438                         mPusher.installModules(
439                                 ImmutableMultimap.of(APEX_PACKAGE_NAME, mFakeApex),
440                                 /*factoryReset=*/ true,
441                                 /*disablePackageCache=*/ false));
442         verify(mMockDevice, never()).executeShellV2Command(TESTHARNESS_ENABLE);
443         verify(mMockDevice, times(1)).pushFile(renamedApex, APEX_PATH_ON_DEVICE);
444     }
445 
446     /** Throws ModulePushError if any file push fails and there are more than one test files. */
447     @Test
testInstallModulesFailureIfAnyPushFails()448     public void testInstallModulesFailureIfAnyPushFails() throws Exception {
449         Path dir = mFakeApex.toPath().getParent();
450         File renamedApex = dir.resolve(APEX_PRELOAD_NAME).toFile();
451         File renamedApk = dir.resolve(APK_PRELOAD_NAME).toFile();
452         when(mMockDevice.pushFile(any(), eq(APEX_PATH_ON_DEVICE))).thenReturn(true);
453         when(mMockDevice.pushFile(any(), eq(APK_PATH_ON_DEVICE))).thenReturn(false);
454         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2", APK_PACKAGE_NAME, "2"));
455 
456         assertThrows(
457                 ModulePusher.ModulePushError.class,
458                 () ->
459                         mPusher.installModules(
460                                 ImmutableMultimap.of(
461                                         APEX_PACKAGE_NAME, mFakeApex, APK_PACKAGE_NAME, mFakeApk),
462                                 /*factoryReset=*/ true,
463                                 /*disablePackageCache=*/ false));
464 
465         verify(mMockDevice, never()).executeShellV2Command(TESTHARNESS_ENABLE);
466         verify(mMockDevice, times(1)).pushFile(renamedApex, APEX_PATH_ON_DEVICE);
467         verify(mMockDevice, times(1)).pushFile(renamedApk, APK_PATH_ON_DEVICE);
468         assertFalse(Files.exists(mFakeApex.toPath()));
469         assertFalse(Files.exists(mFakeApk.toPath()));
470         assertTrue(Files.isRegularFile(renamedApex.toPath()));
471         assertTrue(Files.isRegularFile(renamedApk.toPath()));
472     }
473 
474     /** Throws ModulePushError if activated version code is different. */
475     @Test
testInstallModulesFailureIfActivationVersionCodeDifferent()476     public void testInstallModulesFailureIfActivationVersionCodeDifferent() throws Exception {
477         when(mMockDevice.pushFile(any(), eq(APEX_PATH_ON_DEVICE))).thenReturn(true);
478         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2"));
479         activateVersion(1);
480 
481         assertThrows(
482                 ModulePusher.ModulePushError.class,
483                 () ->
484                         mPusher.installModules(
485                                 ImmutableMultimap.of(APEX_PACKAGE_NAME, mFakeApex),
486                                 /*factoryReset=*/ true,
487                                 /*disablePackageCache=*/ false));
488         verify(mMockDevice, times(1)).pushFile(any(), any());
489     }
490 
491     /** Throws ModulePushError if failed to activate. */
492     @Test
testInstallModulesFailureIfActivationFailed()493     public void testInstallModulesFailureIfActivationFailed() throws Exception {
494         when(mMockDevice.pushFile(any(), eq(APEX_PATH_ON_DEVICE))).thenReturn(true);
495         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "2"));
496         when(mMockDevice.getActiveApexes()).thenReturn(new HashSet<>());
497 
498         assertThrows(
499                 ModulePusher.ModulePushError.class,
500                 () ->
501                         mPusher.installModules(
502                                 ImmutableMultimap.of(APEX_PACKAGE_NAME, mFakeApex),
503                                 /*factoryReset=*/ true,
504                                 /*disablePackageCache=*/ false));
505         verify(mMockDevice, times(1)).pushFile(any(), any());
506     }
507 
508     /** Throws ModulePushError if version code not updated. */
509     @Test
testInstallModulesFailureIfVersionCodeDifferent()510     public void testInstallModulesFailureIfVersionCodeDifferent() throws Exception {
511         when(mMockDevice.pushFile(any(), eq(APEX_PATH_ON_DEVICE))).thenReturn(true);
512         setVersionCodesOnDevice(ImmutableMap.of(APEX_PACKAGE_NAME, "1"));
513         activateVersion(2);
514 
515         assertThrows(
516                 ModulePusher.ModulePushError.class,
517                 () ->
518                         mPusher.installModules(
519                                 ImmutableMultimap.of(APEX_PACKAGE_NAME, mFakeApex),
520                                 /*factoryReset=*/ true,
521                                 /*disablePackageCache=*/ false));
522         verify(mMockDevice, times(1)).pushFile(any(), any());
523     }
524 
setVersionCodesOnDevice(Map<String, String> updatedPackageCodes)525     private void setVersionCodesOnDevice(Map<String, String> updatedPackageCodes) throws Exception {
526         StringBuilder apkSb1 = new StringBuilder();
527         StringBuilder apkSb2 = new StringBuilder();
528         StringBuilder apexSb1 = new StringBuilder();
529         StringBuilder apexSb2 = new StringBuilder();
530         for (String pack : updatedPackageCodes.keySet()) {
531             String old = String.format("package:%s versionCode:%s\n", pack, "1");
532             String updated =
533                     String.format(
534                             "package:%s versionCode:%s\n", pack, updatedPackageCodes.get(pack));
535             if (pack.contains("APEX")) {
536                 apexSb1.append(old);
537                 apexSb2.append(updated);
538             } else {
539                 apkSb1.append(old);
540                 apkSb2.append(updated);
541             }
542         }
543         when(mMockDevice.executeShellV2Command(
544                         "cmd package list packages --apex-only --show-versioncode| grep"
545                                 + " 'com.google'"))
546                 .thenReturn(getCommandResult(apexSb1.toString()))
547                 .thenReturn(getCommandResult(apexSb2.toString()));
548         when(mMockDevice.executeShellV2Command(
549                         "cmd package list packages --show-versioncode| grep 'com.google'"))
550                 .thenReturn(getCommandResult(apkSb1.toString()))
551                 .thenReturn(getCommandResult(apkSb2.toString()));
552     }
553 
activateVersion(long versionCode)554     private void activateVersion(long versionCode) throws DeviceNotAvailableException {
555         ITestDevice.ApexInfo fakeApexData =
556                 new ITestDevice.ApexInfo(APEX_PACKAGE_NAME, versionCode, APEX_PATH_ON_DEVICE);
557         when(mMockDevice.getActiveApexes())
558                 .thenReturn(new HashSet<>(Collections.singletonList(fakeApexData)));
559     }
560 
getCommandResult(String output)561     private static CommandResult getCommandResult(String output) {
562         CommandResult result = new CommandResult();
563         result.setStatus(CommandStatus.SUCCESS);
564         result.setStdout(output);
565         result.setExitCode(0);
566         return result;
567     }
568 }
569