Home | History | Annotate | Download | only in test2
      1 /*
      2  * Copyright (C) 2018 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.framework.multidexlegacytestservices.test2;
     18 
     19 import android.app.ActivityManager;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.support.test.InstrumentationRegistry;
     23 import android.support.test.runner.AndroidJUnit4;
     24 import android.util.Log;
     25 import java.io.File;
     26 import java.io.FileFilter;
     27 import java.io.IOException;
     28 import java.io.RandomAccessFile;
     29 import java.util.concurrent.TimeoutException;
     30 import junit.framework.Assert;
     31 import org.junit.Before;
     32 import org.junit.Test;
     33 import org.junit.runner.RunWith;
     34 
     35 /**
     36  * Run the tests with: <code>adb shell am instrument -w
     37  * com.android.framework.multidexlegacytestservices.test2/android.support.test.runner.AndroidJUnitRunner
     38  * </code>
     39  */
     40 @RunWith(AndroidJUnit4.class)
     41 public class ServicesTests {
     42     private static final String TAG = "ServicesTests";
     43 
     44     static {
     45         Log.i(TAG, "Initializing");
     46     }
     47 
     48     private class ExtensionFilter implements FileFilter {
     49         private final String ext;
     50 
     51         public ExtensionFilter(String ext) {
     52             this.ext = ext;
     53         }
     54 
     55         @Override
     56         public boolean accept(File file) {
     57             return file.getName().endsWith(ext);
     58         }
     59     }
     60 
     61     private class ExtractedZipFilter extends ExtensionFilter {
     62         public  ExtractedZipFilter() {
     63             super(".zip");
     64         }
     65 
     66         @Override
     67         public boolean accept(File file) {
     68             return super.accept(file) && !file.getName().startsWith("tmp-");
     69         }
     70     }
     71 
     72     private static final int ENDHDR = 22;
     73 
     74     private static final String SERVICE_BASE_ACTION =
     75             "com.android.framework.multidexlegacytestservices.action.Service";
     76     private static final int MIN_SERVICE = 1;
     77     private static final int MAX_SERVICE = 19;
     78     private static final String COMPLETION_SUCCESS = "Success";
     79 
     80     private File targetFilesDir;
     81 
     82     @Before
     83     public void setup() throws Exception {
     84         Log.i(TAG, "setup");
     85         killServices();
     86 
     87         File applicationDataDir =
     88                 new File(InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir);
     89         clearDirContent(applicationDataDir);
     90         targetFilesDir = InstrumentationRegistry.getTargetContext().getFilesDir();
     91 
     92         Log.i(TAG, "setup done");
     93     }
     94 
     95     @Test
     96     public void testStressConcurentLaunch() throws Exception {
     97         startServices();
     98         waitServicesCompletion();
     99         String completionStatus = getServicesCompletionStatus();
    100         if (completionStatus != COMPLETION_SUCCESS) {
    101             Assert.fail(completionStatus);
    102         }
    103     }
    104 
    105     @Test
    106     public void testRecoverFromZipCorruption() throws Exception {
    107         int serviceId = 1;
    108         // Ensure extraction.
    109         initServicesWorkFiles();
    110         startService(serviceId);
    111         waitServicesCompletion(serviceId);
    112 
    113         // Corruption of the extracted zips.
    114         tamperAllExtractedZips();
    115 
    116         killServices();
    117         checkRecover();
    118     }
    119 
    120     @Test
    121     public void testRecoverFromDexCorruption() throws Exception {
    122         int serviceId = 1;
    123         // Ensure extraction.
    124         initServicesWorkFiles();
    125         startService(serviceId);
    126         waitServicesCompletion(serviceId);
    127 
    128         // Corruption of the odex files.
    129         tamperAllOdex();
    130 
    131         killServices();
    132         checkRecover();
    133     }
    134 
    135     @Test
    136     public void testRecoverFromZipCorruptionStressTest() throws Exception {
    137         Thread startServices =
    138                 new Thread() {
    139             @Override
    140             public void run() {
    141                 startServices();
    142             }
    143         };
    144 
    145         startServices.start();
    146 
    147         // Start services lasts more than 80s, lets cause a few corruptions during this interval.
    148         for (int i = 0; i < 80; i++) {
    149             Thread.sleep(1000);
    150             tamperAllExtractedZips();
    151         }
    152         startServices.join();
    153         try {
    154             waitServicesCompletion();
    155         } catch (TimeoutException e) {
    156             // Can happen.
    157         }
    158 
    159         killServices();
    160         checkRecover();
    161     }
    162 
    163     @Test
    164     public void testRecoverFromDexCorruptionStressTest() throws Exception {
    165         Thread startServices =
    166                 new Thread() {
    167             @Override
    168             public void run() {
    169                 startServices();
    170             }
    171         };
    172 
    173         startServices.start();
    174 
    175         // Start services lasts more than 80s, lets cause a few corruptions during this interval.
    176         for (int i = 0; i < 80; i++) {
    177             Thread.sleep(1000);
    178             tamperAllOdex();
    179         }
    180         startServices.join();
    181         try {
    182             waitServicesCompletion();
    183         } catch (TimeoutException e) {
    184             // Will probably happen most of the time considering what we're doing...
    185         }
    186 
    187         killServices();
    188         checkRecover();
    189     }
    190 
    191     private static void clearDirContent(File dir) {
    192         for (File subElement : dir.listFiles()) {
    193             if (subElement.isDirectory()) {
    194                 clearDirContent(subElement);
    195             }
    196             if (!subElement.delete()) {
    197                 throw new AssertionError("Failed to clear '" + subElement.getAbsolutePath() + "'");
    198             }
    199         }
    200     }
    201 
    202     private void startServices() {
    203         Log.i(TAG, "start services");
    204         initServicesWorkFiles();
    205         for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
    206             startService(i);
    207             try {
    208                 Thread.sleep((i - 1) * (1 << (i / 5)));
    209             } catch (InterruptedException e) {
    210             }
    211         }
    212     }
    213 
    214     private void startService(int serviceId) {
    215         Log.i(TAG, "start service " + serviceId);
    216         InstrumentationRegistry.getContext().startService(new Intent(SERVICE_BASE_ACTION + serviceId));
    217     }
    218 
    219     private void initServicesWorkFiles() {
    220         for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
    221             File resultFile = new File(targetFilesDir, "Service" + i);
    222             resultFile.delete();
    223             Assert.assertFalse(
    224                     "Failed to delete result file '" + resultFile.getAbsolutePath() + "'.",
    225                     resultFile.exists());
    226             File completeFile = new File(targetFilesDir, "Service" + i + ".complete");
    227             completeFile.delete();
    228             Assert.assertFalse(
    229                     "Failed to delete completion file '" + completeFile.getAbsolutePath() + "'.",
    230                     completeFile.exists());
    231         }
    232     }
    233 
    234     private void waitServicesCompletion() throws TimeoutException {
    235         Log.i(TAG, "start sleeping");
    236         int attempt = 0;
    237         int maxAttempt = 50; // 10 is enough for a nexus S
    238         do {
    239             try {
    240                 Thread.sleep(5000);
    241             } catch (InterruptedException e) {
    242             }
    243             attempt++;
    244             if (attempt >= maxAttempt) {
    245                 throw new TimeoutException();
    246             }
    247         } while (!areAllServicesCompleted());
    248     }
    249 
    250     private void waitServicesCompletion(int serviceId) throws TimeoutException {
    251         Log.i(TAG, "start sleeping");
    252         int attempt = 0;
    253         int maxAttempt = 50; // 10 is enough for a nexus S
    254         do {
    255             try {
    256                 Thread.sleep(5000);
    257             } catch (InterruptedException e) {
    258             }
    259             attempt++;
    260             if (attempt >= maxAttempt) {
    261                 throw new TimeoutException();
    262             }
    263         } while (isServiceRunning(serviceId));
    264     }
    265 
    266     private String getServicesCompletionStatus() {
    267         String status = COMPLETION_SUCCESS;
    268         for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
    269             File resultFile = new File(targetFilesDir, "Service" + i);
    270             if (!resultFile.isFile()) {
    271                 status = "Service" + i + " never completed.";
    272                 break;
    273             }
    274             if (resultFile.length() != 8) {
    275                 status = "Service" + i + " was restarted.";
    276                 break;
    277             }
    278         }
    279         Log.i(TAG, "Services completion status: " + status);
    280         return status;
    281     }
    282 
    283     private String getServiceCompletionStatus(int serviceId) {
    284         String status = COMPLETION_SUCCESS;
    285         File resultFile = new File(targetFilesDir, "Service" + serviceId);
    286         if (!resultFile.isFile()) {
    287             status = "Service" + serviceId + " never completed.";
    288         } else if (resultFile.length() != 8) {
    289             status = "Service" + serviceId + " was restarted.";
    290         }
    291         Log.i(TAG, "Service " + serviceId + " completion status: " + status);
    292         return status;
    293     }
    294 
    295     private boolean areAllServicesCompleted() {
    296         for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) {
    297             if (isServiceRunning(i)) {
    298                 return false;
    299             }
    300         }
    301         return true;
    302     }
    303 
    304     private boolean isServiceRunning(int i) {
    305         File completeFile = new File(targetFilesDir, "Service" + i + ".complete");
    306         return !completeFile.exists();
    307     }
    308 
    309     private File getSecondaryFolder() {
    310         File dir =
    311                 new File(
    312                         new File(
    313                                 InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir,
    314                                 "code_cache"),
    315                         "secondary-dexes");
    316         Assert.assertTrue(dir.getAbsolutePath(), dir.isDirectory());
    317         return dir;
    318     }
    319 
    320     private void tamperAllExtractedZips() throws IOException {
    321         // First attempt was to just overwrite zip entries but keep central directory, this was no
    322         // trouble for Dalvik that was just ignoring those zip and using the odex files.
    323         Log.i(TAG, "Tamper extracted zip files by overwriting all content by '\\0's.");
    324         byte[] zeros = new byte[4 * 1024];
    325         // Do not tamper tmp zip during their extraction.
    326         for (File zip : getSecondaryFolder().listFiles(new ExtractedZipFilter())) {
    327             long fileLength = zip.length();
    328             Assert.assertTrue(fileLength > ENDHDR);
    329             zip.setWritable(true);
    330             RandomAccessFile raf = new RandomAccessFile(zip, "rw");
    331             try {
    332                 int index = 0;
    333                 while (index < fileLength) {
    334                     int length = (int) Math.min(zeros.length, fileLength - index);
    335                     raf.write(zeros, 0, length);
    336                     index += length;
    337                 }
    338             } finally {
    339                 raf.close();
    340             }
    341         }
    342     }
    343 
    344     private void tamperAllOdex() throws IOException {
    345         Log.i(TAG, "Tamper odex files by overwriting some content by '\\0's.");
    346         byte[] zeros = new byte[4 * 1024];
    347         // I think max size would be 40 (u1[8] + 8 u4) but it's a test so lets take big margins.
    348         int savedSizeForOdexHeader = 80;
    349         for (File odex : getSecondaryFolder().listFiles(new ExtensionFilter(".dex"))) {
    350             long fileLength = odex.length();
    351             Assert.assertTrue(fileLength > zeros.length + savedSizeForOdexHeader);
    352             odex.setWritable(true);
    353             RandomAccessFile raf = new RandomAccessFile(odex, "rw");
    354             try {
    355                 raf.seek(savedSizeForOdexHeader);
    356                 raf.write(zeros, 0, zeros.length);
    357             } finally {
    358                 raf.close();
    359             }
    360         }
    361     }
    362 
    363     private void checkRecover() throws TimeoutException {
    364         Log.i(TAG, "Check recover capability");
    365         int serviceId = 1;
    366         // Start one service and check it was able to run correctly even if a previous run failed.
    367         initServicesWorkFiles();
    368         startService(serviceId);
    369         waitServicesCompletion(serviceId);
    370         String completionStatus = getServiceCompletionStatus(serviceId);
    371         if (completionStatus != COMPLETION_SUCCESS) {
    372             Assert.fail(completionStatus);
    373         }
    374     }
    375 
    376     private void killServices() {
    377         ((ActivityManager)
    378                 InstrumentationRegistry.getContext().getSystemService(Context.ACTIVITY_SERVICE))
    379         .killBackgroundProcesses("com.android.framework.multidexlegacytestservices");
    380     }
    381 }
    382