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