Home | History | Annotate | Download | only in dumprendertree2
      1 /*
      2  * Copyright (C) 2010 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.dumprendertree2;
     18 
     19 import android.app.Service;
     20 import android.content.Intent;
     21 import android.os.Bundle;
     22 import android.os.Environment;
     23 import android.os.Handler;
     24 import android.os.IBinder;
     25 import android.os.Message;
     26 import android.os.Messenger;
     27 import android.util.Log;
     28 
     29 import java.io.File;
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 
     33 /**
     34  * A service that handles managing the results of tests, informing of crashes, generating
     35  * summaries, etc.
     36  */
     37 public class ManagerService extends Service {
     38 
     39     private static final String LOG_TAG = "ManagerService";
     40 
     41     private static final int MSG_CRASH_TIMEOUT_EXPIRED = 0;
     42     private static final int MSG_SUMMARIZER_DONE = 1;
     43 
     44     private static final int CRASH_TIMEOUT_MS = 20 * 1000;
     45 
     46     /** TODO: make it a setting */
     47     static final String RESULTS_ROOT_DIR_PATH =
     48             Environment.getExternalStorageDirectory() + File.separator + "layout-test-results";
     49 
     50     /** TODO: Make it a setting */
     51     private static final List<String> EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES =
     52             new ArrayList<String>(3);
     53     {
     54         EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("platform" + File.separator +
     55                 "android-v8" + File.separator);
     56         EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("platform" + File.separator +
     57                 "android" + File.separator);
     58         EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.add("");
     59     }
     60 
     61     /** TODO: Make these settings */
     62     private static final String TEXT_RESULT_EXTENSION = "txt";
     63     private static final String IMAGE_RESULT_EXTENSION = "png";
     64 
     65     static final int MSG_PROCESS_ACTUAL_RESULTS = 0;
     66     static final int MSG_ALL_TESTS_FINISHED = 1;
     67     static final int MSG_FIRST_TEST = 2;
     68     static final int MSG_CURRENT_TEST_CRASHED = 3;
     69     static final int MSG_RESET = 4;
     70 
     71     /**
     72      * This handler is purely for IPC. It is used to create mMessenger
     73      * that generates a binder returned in onBind method.
     74      */
     75     private Handler mIncomingHandler = new Handler() {
     76         @Override
     77         public void handleMessage(Message msg) {
     78             switch (msg.what) {
     79                 case MSG_RESET:
     80                     mSummarizer.reset();
     81                     break;
     82 
     83                 case MSG_FIRST_TEST:
     84                     Bundle bundle = msg.getData();
     85                     ensureNextTestSetup(bundle.getString("firstTest"), bundle.getInt("index"));
     86                     break;
     87 
     88                 case MSG_PROCESS_ACTUAL_RESULTS:
     89                     Log.d(LOG_TAG,"mIncomingHandler: " + msg.getData().getString("relativePath"));
     90                     onActualResultsObtained(msg.getData());
     91                     break;
     92 
     93                 case MSG_CURRENT_TEST_CRASHED:
     94                     mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
     95                     onTestCrashed();
     96                     break;
     97 
     98                 case MSG_ALL_TESTS_FINISHED:
     99                     /** We run it in a separate thread to avoid ANR */
    100                     new Thread() {
    101                         @Override
    102                         public void run() {
    103                             mSummarizer.setTestsRelativePath(mAllTestsRelativePath);
    104                             Message msg = Message.obtain(mInternalMessagesHandler,
    105                                     MSG_SUMMARIZER_DONE);
    106                             mSummarizer.summarize(msg);
    107                         }
    108                     }.start();
    109             }
    110         }
    111     };
    112 
    113     private Messenger mMessenger = new Messenger(mIncomingHandler);
    114 
    115     private Handler mInternalMessagesHandler = new Handler() {
    116         @Override
    117         public void handleMessage(Message msg) {
    118             switch (msg.what) {
    119                 case MSG_CRASH_TIMEOUT_EXPIRED:
    120                     onTestCrashed();
    121                     break;
    122 
    123                 case MSG_SUMMARIZER_DONE:
    124                     Intent intent = new Intent(ManagerService.this, TestsListActivity.class);
    125                     intent.setAction(Intent.ACTION_SHUTDOWN);
    126                     /** This flag is needed because we send the intent from the service */
    127                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    128                     startActivity(intent);
    129                     break;
    130             }
    131         }
    132     };
    133 
    134     private Summarizer mSummarizer;
    135 
    136     private String mCurrentlyRunningTest;
    137     private int mCurrentlyRunningTestIndex;
    138 
    139     /**
    140      * These are implementation details of getExpectedResultPath() used to reduce the number
    141      * of requests required to the host server.
    142      */
    143     private String mLastExpectedResultPathRequested;
    144     private String mLastExpectedResultPathFetched;
    145 
    146     private String mAllTestsRelativePath;
    147 
    148     @Override
    149     public void onCreate() {
    150         super.onCreate();
    151 
    152         mSummarizer = new Summarizer(RESULTS_ROOT_DIR_PATH, getApplicationContext());
    153     }
    154 
    155     @Override
    156     public int onStartCommand(Intent intent, int flags, int startId) {
    157         mAllTestsRelativePath = intent.getStringExtra("path");
    158         assert mAllTestsRelativePath != null;
    159         return START_STICKY;
    160     }
    161 
    162     @Override
    163     public IBinder onBind(Intent intent) {
    164         return mMessenger.getBinder();
    165     }
    166 
    167     private void onActualResultsObtained(Bundle bundle) {
    168         mInternalMessagesHandler.removeMessages(MSG_CRASH_TIMEOUT_EXPIRED);
    169         ensureNextTestSetup(bundle.getString("nextTest"), bundle.getInt("testIndex") + 1);
    170 
    171         AbstractResult results =
    172                 AbstractResult.TestType.valueOf(bundle.getString("type")).createResult(bundle);
    173 
    174         Log.i(LOG_TAG, "onActualResultObtained: " + results.getRelativePath());
    175         handleResults(results);
    176     }
    177 
    178     private void ensureNextTestSetup(String nextTest, int index) {
    179         if (nextTest == null) {
    180             Log.w(LOG_TAG, "ensureNextTestSetup(): nextTest=null");
    181             return;
    182         }
    183 
    184         mCurrentlyRunningTest = nextTest;
    185         mCurrentlyRunningTestIndex = index;
    186         mInternalMessagesHandler.sendEmptyMessageDelayed(MSG_CRASH_TIMEOUT_EXPIRED, CRASH_TIMEOUT_MS);
    187     }
    188 
    189     /**
    190      * This sends an intent to TestsListActivity to restart LayoutTestsExecutor.
    191      * The more detailed description of the flow is in the comment of onNewIntent
    192      * method in TestsListActivity.
    193      */
    194     private void onTestCrashed() {
    195         handleResults(new CrashedDummyResult(mCurrentlyRunningTest));
    196 
    197         Log.w(LOG_TAG, "onTestCrashed(): " + mCurrentlyRunningTest +
    198                 " (" + mCurrentlyRunningTestIndex + ")");
    199 
    200         Intent intent = new Intent(this, TestsListActivity.class);
    201         intent.setAction(Intent.ACTION_REBOOT);
    202         /** This flag is needed because we send the intent from the service */
    203         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    204         intent.putExtra("crashedTestIndex", mCurrentlyRunningTestIndex);
    205         startActivity(intent);
    206     }
    207 
    208     private void handleResults(AbstractResult results) {
    209         String relativePath = results.getRelativePath();
    210         results.setExpectedTextResult(getExpectedTextResult(relativePath));
    211         results.setExpectedTextResultPath(getExpectedTextResultPath(relativePath));
    212         results.setExpectedImageResult(getExpectedImageResult(relativePath));
    213         results.setExpectedImageResultPath(getExpectedImageResultPath(relativePath));
    214 
    215         dumpActualTextResult(results);
    216         dumpActualImageResult(results);
    217 
    218         mSummarizer.appendTest(results);
    219     }
    220 
    221     private void dumpActualTextResult(AbstractResult result) {
    222         String testPath = result.getRelativePath();
    223         String actualTextResult = result.getActualTextResult();
    224         if (actualTextResult == null) {
    225             return;
    226         }
    227 
    228         String resultPath = FileFilter.setPathEnding(testPath, "-actual." + TEXT_RESULT_EXTENSION);
    229         FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
    230                 actualTextResult.getBytes(), false);
    231     }
    232 
    233     private void dumpActualImageResult(AbstractResult result) {
    234         String testPath = result.getRelativePath();
    235         byte[] actualImageResult = result.getActualImageResult();
    236         if (actualImageResult == null) {
    237             return;
    238         }
    239 
    240         String resultPath = FileFilter.setPathEnding(testPath,
    241                 "-actual." + IMAGE_RESULT_EXTENSION);
    242         FsUtils.writeDataToStorage(new File(RESULTS_ROOT_DIR_PATH, resultPath),
    243                 actualImageResult, false);
    244     }
    245 
    246     public String getExpectedTextResult(String relativePath) {
    247         byte[] result = getExpectedResult(relativePath, TEXT_RESULT_EXTENSION);
    248         if (result != null) {
    249             return new String(result);
    250         }
    251         return null;
    252     }
    253 
    254     public byte[] getExpectedImageResult(String relativePath) {
    255         return getExpectedResult(relativePath, IMAGE_RESULT_EXTENSION);
    256     }
    257 
    258     private byte[] getExpectedResult(String relativePath, String extension) {
    259         String originalRelativePath =
    260                 FileFilter.setPathEnding(relativePath, "-expected." + extension);
    261         mLastExpectedResultPathRequested = originalRelativePath;
    262 
    263         byte[] bytes = null;
    264         List<String> locations = EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES;
    265 
    266         int size = EXPECTED_RESULT_LOCATION_RELATIVE_DIR_PREFIXES.size();
    267         for (int i = 0; bytes == null && i < size; i++) {
    268             relativePath = locations.get(i) + originalRelativePath;
    269             bytes = FsUtils.readDataFromUrl(FileFilter.getUrl(relativePath, false));
    270         }
    271 
    272         mLastExpectedResultPathFetched = bytes == null ? null : relativePath;
    273         return bytes;
    274     }
    275 
    276     private String getExpectedTextResultPath(String relativePath) {
    277         return getExpectedResultPath(relativePath, TEXT_RESULT_EXTENSION);
    278     }
    279 
    280     private String getExpectedImageResultPath(String relativePath) {
    281         return getExpectedResultPath(relativePath, IMAGE_RESULT_EXTENSION);
    282     }
    283 
    284     private String getExpectedResultPath(String relativePath, String extension) {
    285         String originalRelativePath =
    286             FileFilter.setPathEnding(relativePath, "-expected." + extension);
    287         if (!originalRelativePath.equals(mLastExpectedResultPathRequested)) {
    288             getExpectedResult(relativePath, extension);
    289         }
    290 
    291         return mLastExpectedResultPathFetched;
    292     }
    293 }
    294