Home | History | Annotate | Download | only in cts
      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 android.webkit.cts;
     18 
     19 import android.test.ActivityInstrumentationTestCase2;
     20 import android.test.UiThreadTest;
     21 import android.webkit.TracingConfig;
     22 import android.webkit.TracingController;
     23 import android.webkit.WebView;
     24 import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
     25 
     26 import com.android.compatibility.common.util.NullWebViewUtils;
     27 import com.android.compatibility.common.util.PollingCheck;
     28 
     29 import java.io.ByteArrayOutputStream;
     30 import java.io.IOException;
     31 import java.io.OutputStream;
     32 import java.util.concurrent.atomic.AtomicInteger;
     33 import java.util.concurrent.Callable;
     34 import java.util.concurrent.Executor;
     35 import java.util.concurrent.ExecutorService;
     36 import java.util.concurrent.Executors;
     37 import java.util.concurrent.ThreadFactory;
     38 import java.util.concurrent.TimeUnit;
     39 
     40 public class TracingControllerTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
     41 
     42     public static class TracingReceiver extends OutputStream {
     43         private int mChunkCount;
     44         private boolean mComplete;
     45         private ByteArrayOutputStream outputStream;
     46 
     47         public TracingReceiver() {
     48             outputStream = new ByteArrayOutputStream();
     49         }
     50 
     51         @Override
     52         public void write(byte[] chunk) {
     53             validateThread();
     54             mChunkCount++;
     55             try {
     56                 outputStream.write(chunk);
     57             } catch (IOException e) {
     58                 throw new RuntimeException(e);
     59             }
     60         }
     61 
     62         @Override
     63         public void close() {
     64             validateThread();
     65             mComplete = true;
     66         }
     67 
     68         @Override
     69         public void flush() {
     70             fail("flush should not be called");
     71         }
     72 
     73         @Override
     74         public void write(int b) {
     75             fail("write(int) should not be called");
     76         }
     77 
     78         @Override
     79         public void write(byte[] b, int off, int len) {
     80             fail("write(byte[], int, int) should not be called");
     81         }
     82 
     83         private void validateThread() {
     84             // Ensure the callbacks are called on the correct (executor) thread.
     85             assertTrue(Thread.currentThread().getName().startsWith(EXECUTOR_THREAD_PREFIX));
     86         }
     87 
     88         int getNbChunks() { return mChunkCount; }
     89         boolean getComplete() { return mComplete; }
     90 
     91         Callable<Boolean> getCompleteCallable() {
     92             return new Callable<Boolean>() {
     93                 @Override
     94                 public Boolean call() {
     95                     return getComplete();
     96                 }
     97             };
     98         }
     99 
    100         ByteArrayOutputStream getOutputStream() { return outputStream; }
    101     }
    102 
    103     private static final int POLLING_TIMEOUT = 60 * 1000;
    104     private static final int EXECUTOR_TIMEOUT = 10; // timeout of executor shutdown in seconds
    105     private static final String EXECUTOR_THREAD_PREFIX = "TracingExecutorThread";
    106     private WebViewOnUiThread mOnUiThread;
    107     private ExecutorService singleThreadExecutor;
    108 
    109     public TracingControllerTest() throws Exception {
    110         super("android.webkit.cts", WebViewCtsActivity.class);
    111     }
    112 
    113     @Override
    114     protected void setUp() throws Exception {
    115         super.setUp();
    116         WebView webview = getActivity().getWebView();
    117         if (webview == null) return;
    118         mOnUiThread = new WebViewOnUiThread(this, webview);
    119         singleThreadExecutor = Executors.newSingleThreadExecutor(getCustomThreadFactory());
    120     }
    121 
    122     @Override
    123     protected void tearDown() throws Exception {
    124         // make sure to stop everything and clean up
    125         ensureTracingStopped();
    126         if (singleThreadExecutor != null) {
    127             singleThreadExecutor.shutdown();
    128             if (!singleThreadExecutor.awaitTermination(EXECUTOR_TIMEOUT, TimeUnit.SECONDS)) {
    129                 fail("Failed to shutdown executor");
    130             }
    131         }
    132         if (mOnUiThread != null) {
    133             mOnUiThread.cleanUp();
    134         }
    135         super.tearDown();
    136     }
    137 
    138     private void ensureTracingStopped() throws Exception {
    139         if (!NullWebViewUtils.isWebViewAvailable()) {
    140             return;
    141         }
    142 
    143         TracingController.getInstance().stop(null, singleThreadExecutor);
    144         Callable<Boolean> tracingStopped = new Callable<Boolean>() {
    145             @Override
    146             public Boolean call() {
    147                 return !TracingController.getInstance().isTracing();
    148             }
    149         };
    150         PollingCheck.check("Tracing did not stop", POLLING_TIMEOUT, tracingStopped);
    151     }
    152 
    153     private ThreadFactory getCustomThreadFactory() {
    154         return new ThreadFactory() {
    155             private final AtomicInteger threadCount = new AtomicInteger(0);
    156             @Override
    157             public Thread newThread(Runnable r) {
    158                 Thread thread = new Thread(r);
    159                 thread.setName(EXECUTOR_THREAD_PREFIX + "_" + threadCount.incrementAndGet());
    160                 return thread;
    161             }
    162         };
    163     }
    164 
    165     // Test that callbacks are invoked and tracing data is returned on the correct thread
    166     // (via executor). Tracing start/stop and webview loading happens on the UI thread.
    167     public void testTracingControllerCallbacksOnUI() throws Throwable {
    168         if (!NullWebViewUtils.isWebViewAvailable()) {
    169             return;
    170         }
    171         final TracingReceiver tracingReceiver = new TracingReceiver();
    172 
    173         runTestOnUiThread(new Runnable() {
    174             @Override
    175             public void run() {
    176                 runTracingTestWithCallbacks(tracingReceiver, singleThreadExecutor);
    177             }
    178         });
    179         PollingCheck.check("Tracing did not complete", POLLING_TIMEOUT, tracingReceiver.getCompleteCallable());
    180         assertTrue(tracingReceiver.getNbChunks() > 0);
    181         assertTrue(tracingReceiver.getOutputStream().size() > 0);
    182         // currently the output is json (as of April 2018), but this could change in the future
    183         // so we don't explicitly test the contents of output stream.
    184     }
    185 
    186     // Test that callbacks are invoked and tracing data is returned on the correct thread
    187     // (via executor). Tracing start/stop happens on the testing thread; webview loading
    188     // happens on the UI thread.
    189     public void testTracingControllerCallbacks() throws Throwable {
    190         if (!NullWebViewUtils.isWebViewAvailable()) {
    191             return;
    192         }
    193 
    194         final TracingReceiver tracingReceiver = new TracingReceiver();
    195         runTracingTestWithCallbacks(tracingReceiver, singleThreadExecutor);
    196         PollingCheck.check("Tracing did not complete", POLLING_TIMEOUT, tracingReceiver.getCompleteCallable());
    197         assertTrue(tracingReceiver.getNbChunks() > 0);
    198         assertTrue(tracingReceiver.getOutputStream().size() > 0);
    199     }
    200 
    201     // Test that tracing stop has no effect if tracing has not been started.
    202     public void testTracingStopFalseIfNotTracing() {
    203         if (!NullWebViewUtils.isWebViewAvailable()) {
    204             return;
    205         }
    206 
    207         TracingController tracingController = TracingController.getInstance();
    208         assertFalse(tracingController.stop(null, singleThreadExecutor));
    209         assertFalse(tracingController.isTracing());
    210     }
    211 
    212     // Test that tracing cannot be started if already tracing.
    213     public void testTracingCannotStartIfAlreadyTracing() throws Exception {
    214         if (!NullWebViewUtils.isWebViewAvailable()) {
    215             return;
    216         }
    217 
    218         TracingController tracingController = TracingController.getInstance();
    219         TracingConfig config = new TracingConfig.Builder().build();
    220 
    221         tracingController.start(config);
    222         assertTrue(tracingController.isTracing());
    223         try {
    224             tracingController.start(config);
    225         } catch (IllegalStateException e) {
    226             // as expected
    227             return;
    228         }
    229         assertTrue(tracingController.stop(null, singleThreadExecutor));
    230         fail("Tracing start should throw an exception when attempting to start while already tracing");
    231     }
    232 
    233     // Test that tracing cannot be invoked with excluded categories.
    234     public void testTracingInvalidCategoriesPatternExclusion() {
    235         if (!NullWebViewUtils.isWebViewAvailable()) {
    236             return;
    237         }
    238 
    239         TracingController tracingController = TracingController.getInstance();
    240         TracingConfig config = new TracingConfig.Builder()
    241                 .addCategories("android_webview","-blink")
    242                 .build();
    243         try {
    244             tracingController.start(config);
    245         } catch (IllegalArgumentException e) {
    246             // as expected;
    247             assertFalse(tracingController.isTracing());
    248             return;
    249         }
    250 
    251         fail("Tracing start should throw an exception due to invalid category pattern");
    252     }
    253 
    254     // Test that tracing cannot be invoked with categories containing commas.
    255     public void testTracingInvalidCategoriesPatternComma() {
    256         if (!NullWebViewUtils.isWebViewAvailable()) {
    257             return;
    258         }
    259 
    260         TracingController tracingController = TracingController.getInstance();
    261         TracingConfig config = new TracingConfig.Builder()
    262                 .addCategories("android_webview, blink")
    263                 .build();
    264         try {
    265             tracingController.start(config);
    266         } catch (IllegalArgumentException e) {
    267             // as expected;
    268             assertFalse(tracingController.isTracing());
    269             return;
    270         }
    271 
    272         fail("Tracing start should throw an exception due to invalid category pattern");
    273     }
    274 
    275     // Test that tracing cannot start with a configuration that is null.
    276     public void testTracingWithNullConfig() {
    277         if (!NullWebViewUtils.isWebViewAvailable()) {
    278             return;
    279         }
    280 
    281         TracingController tracingController = TracingController.getInstance();
    282         try {
    283             tracingController.start(null);
    284         } catch (IllegalArgumentException e) {
    285             // as expected
    286             assertFalse(tracingController.isTracing());
    287             return;
    288         }
    289         fail("Tracing start should throw exception if TracingConfig is null");
    290     }
    291 
    292     // Generic helper function for running tracing.
    293     private void runTracingTestWithCallbacks(TracingReceiver tracingReceiver, Executor executor) {
    294         TracingController tracingController = TracingController.getInstance();
    295         assertNotNull(tracingController);
    296 
    297         TracingConfig config = new TracingConfig.Builder()
    298                 .addCategories(TracingConfig.CATEGORIES_WEB_DEVELOPER)
    299                 .setTracingMode(TracingConfig.RECORD_CONTINUOUSLY)
    300                 .build();
    301         assertFalse(tracingController.isTracing());
    302         tracingController.start(config);
    303         assertTrue(tracingController.isTracing());
    304 
    305         mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
    306         assertTrue(tracingController.stop(tracingReceiver, executor));
    307     }
    308 }
    309 
    310