1 /*
2  * Copyright (C) 2012 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.cts.browser;
18 
19 import android.content.Intent;
20 import android.content.pm.PackageManager;
21 import android.cts.util.WatchDog;
22 import android.net.Uri;
23 import android.provider.Browser;
24 import android.util.Log;
25 import android.webkit.cts.CtsTestServer;
26 
27 import android.cts.util.CtsAndroidTestCase;
28 import com.android.cts.util.ResultType;
29 import com.android.cts.util.ResultUnit;
30 import com.android.cts.util.Stat;
31 import com.android.cts.util.TimeoutReq;
32 
33 import java.net.URLDecoder;
34 import java.util.LinkedHashMap;
35 import java.util.Map;
36 import java.util.concurrent.CountDownLatch;
37 import java.util.concurrent.TimeUnit;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40 
41 import org.apache.http.HttpRequest;
42 import org.apache.http.HttpResponse;
43 import org.apache.http.RequestLine;
44 /**
45  * Browser benchmarking.
46  * It launches an activity with URL and wait for POST from the client.
47  */
48 public class BrowserBenchTest extends CtsAndroidTestCase {
49     private static final String TAG = BrowserBenchTest.class.getSimpleName();
50     private static final boolean DEBUG = false;
51     private static final String OCTANE_START_FILE = "octane/index.html";
52     private static final String ROBOHORNET_START_FILE = "robohornet/robohornet.html";
53     private static final String HOST_COMPLETION_BROADCAST = "com.android.cts.browser.completion";
54     // time-out for watch-dog. POST should happen within this time.
55     private static long BROWSER_POST_TIMEOUT_IN_MS = 10 * 60 * 1000L;
56     // watch-dog will time-out first. So make it long enough.
57     private static long BROWSER_COMPLETION_TIMEOUT_IN_MS = 60 * 60 * 1000L;
58     private static final String HTTP_USER_AGENT = "User-Agent";
59     private CtsTestServer mWebServer;
60     // used for final score
61     private ResultType mTypeNonFinal = ResultType.NEUTRAL;
62     private ResultUnit mUnitNonFinal = ResultUnit.NONE;
63     // used for all other scores
64     private ResultType mTypeFinal = ResultType.NEUTRAL;
65     private ResultUnit mUnitFinal = ResultUnit.SCORE;
66     private WatchDog mWatchDog;
67     private CountDownLatch mLatch;
68     // can be changed by each test before starting
69     private volatile int mNumberRepeat;
70     /** tells how many tests have run up to now */
71     private volatile int mRunIndex;
72     /** stores results for each runs. last entry will be the final score. */
73     private LinkedHashMap<String, double[]> mResultsMap;
74     private PackageManager mPackageManager;
75 
76     @Override
setUp()77     protected void setUp() throws Exception {
78         super.setUp();
79         mPackageManager = getInstrumentation().getContext().getPackageManager();
80         mWebServer = new CtsTestServer(getContext()) {
81             @Override
82             protected HttpResponse onPost(HttpRequest request) throws Exception {
83                 // post uri will look like "cts_report.html?final=1&score=10.1&message=hello"
84                 RequestLine requestLine = request.getRequestLine();
85                 String uriString = URLDecoder.decode(requestLine.getUri(), "UTF-8");
86                 if (DEBUG) {
87                     Log.i(TAG, "uri:" + uriString);
88                 }
89                 String resultRe =
90                         ".*cts_report.html\\?final=([\\d])&score=([\\d]+\\.?[\\d]*)&message=([\\w][\\w ]*)";
91                 Pattern resultPattern = Pattern.compile(resultRe);
92                 Matcher matchResult = resultPattern.matcher(uriString);
93                 if (matchResult.find()) {
94                     int isFinal = Integer.parseInt(matchResult.group(1));
95                     double score = Double.parseDouble(matchResult.group(2));
96                     String message = matchResult.group(3);
97                     Log.i(TAG, message + ":" + score);
98                     if (!mResultsMap.containsKey(message)) {
99                         mResultsMap.put(message, new double[mNumberRepeat]);
100                     }
101                     double[] scores = mResultsMap.get(message);
102                     scores[mRunIndex] = score;
103                     if (isFinal == 1) {
104                         String userAgent = request.getFirstHeader(HTTP_USER_AGENT).getValue();
105                         getReportLog().printValue(HTTP_USER_AGENT + "=" + userAgent, 0,
106                                 ResultType.NEUTRAL, ResultUnit.NONE);
107                         mLatch.countDown();
108                     }
109                     mWatchDog.reset();
110                 }
111                 return null; // default response is OK as it will be ignored by client anyway.
112             }
113         };
114         mResultsMap = new LinkedHashMap<String, double[]>();
115         mWatchDog = new WatchDog(BROWSER_POST_TIMEOUT_IN_MS);
116         mWatchDog.start();
117     }
118 
119     @Override
tearDown()120     protected void tearDown() throws Exception {
121         mWatchDog.stop();
122         mWebServer.shutdown();
123         mWebServer = null;
124         mResultsMap = null;
125         super.tearDown();
126     }
127 
128     @TimeoutReq(minutes = 60)
testOctane()129     public void testOctane() throws InterruptedException {
130         if (!isBrowserSupported()) {
131             Log.i(TAG, "Skipping test for device with no supported browser");
132             return;
133         }
134         String url = mWebServer.getAssetUrl(OCTANE_START_FILE) + "?auto=1";
135         final int kRepeat = 5;
136         doTest(url, ResultType.LOWER_BETTER, ResultUnit.MS,
137                 ResultType.HIGHER_BETTER, ResultUnit.SCORE, kRepeat);
138     }
139 
doTest(String url, ResultType typeNonFinal, ResultUnit unitNonFinal, ResultType typeFinal, ResultUnit unitFinal, int numberRepeat)140     private void doTest(String url, ResultType typeNonFinal, ResultUnit unitNonFinal,
141             ResultType typeFinal, ResultUnit unitFinal, int numberRepeat)
142                     throws InterruptedException {
143         mTypeNonFinal = typeNonFinal;
144         mUnitNonFinal = unitNonFinal;
145         mTypeFinal = typeFinal;
146         mUnitFinal = unitFinal;
147         mNumberRepeat = numberRepeat;
148         Uri uri = Uri.parse(url);
149         for (mRunIndex = 0; mRunIndex < numberRepeat; mRunIndex++) {
150             Log.i(TAG, mRunIndex + "-th round");
151             mLatch = new CountDownLatch(1);
152             Intent intent = new Intent(Intent.ACTION_VIEW, uri);
153             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
154             // force using only one window or tab
155             intent.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
156             getContext().startActivity(intent);
157             boolean ok = mLatch.await(BROWSER_COMPLETION_TIMEOUT_IN_MS, TimeUnit.MILLISECONDS);
158             assertTrue("timed-out", ok);
159         }
160         // it is somewhat awkward to handle the last one specially with Map
161         int numberEntries = mResultsMap.size();
162         int numberToProcess = 1;
163         for (Map.Entry<String, double[]> entry : mResultsMap.entrySet()) {
164             String message = entry.getKey();
165             double[] scores = entry.getValue();
166             if (numberToProcess == numberEntries) { // final score
167                 // store the whole results first
168                 getReportLog().printArray(message, scores, mTypeFinal, mUnitFinal);
169                 getReportLog().printSummary(message, Stat.getAverage(scores), mTypeFinal,
170                         mUnitFinal);
171             } else { // interim results
172                 getReportLog().printArray(message, scores, mTypeNonFinal, mUnitNonFinal);
173             }
174             numberToProcess++;
175         }
176     }
177 
178     /**
179      * @return true iff this device is has a working browser.
180      */
isBrowserSupported()181     private boolean isBrowserSupported() {
182         return !(mPackageManager.hasSystemFeature("android.hardware.type.television")
183                  || mPackageManager.hasSystemFeature("android.software.leanback")
184                  || mPackageManager.hasSystemFeature("android.hardware.type.watch"));
185     }
186 }
187