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 androidx.webkit; 18 19 import static org.hamcrest.MatcherAssert.assertThat; 20 import static org.hamcrest.Matchers.greaterThan; 21 22 import androidx.test.ext.junit.runners.AndroidJUnit4; 23 import androidx.test.filters.LargeTest; 24 import androidx.webkit.test.common.PollingCheck; 25 import androidx.webkit.test.common.WebViewOnUiThread; 26 import androidx.webkit.test.common.WebkitUtils; 27 28 import org.junit.After; 29 import org.junit.Assert; 30 import org.junit.Before; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 34 import java.io.ByteArrayOutputStream; 35 import java.io.IOException; 36 import java.io.OutputStream; 37 import java.util.Arrays; 38 import java.util.List; 39 import java.util.concurrent.Callable; 40 import java.util.concurrent.Executor; 41 import java.util.concurrent.ExecutorService; 42 import java.util.concurrent.Executors; 43 import java.util.concurrent.ThreadFactory; 44 import java.util.concurrent.TimeUnit; 45 import java.util.concurrent.atomic.AtomicInteger; 46 47 @LargeTest 48 @RunWith(AndroidJUnit4.class) 49 public class TracingControllerTest { 50 private TracingController mTracingController; 51 private WebViewOnUiThread mWebViewOnUiThread; 52 private ExecutorService mSingleThreadExecutor; 53 54 private static final String EXECUTOR_THREAD_PREFIX = "TracingExecutorThread"; 55 private static final int POLLING_TIMEOUT = 60 * 1000; 56 private static final int EXECUTOR_TIMEOUT = 10; // timeout of executor shutdown in seconds 57 58 @Before setUp()59 public void setUp() { 60 WebkitUtils.checkFeature(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE); 61 62 mWebViewOnUiThread = new WebViewOnUiThread(); 63 mSingleThreadExecutor = Executors.newSingleThreadExecutor(getCustomThreadFactory()); 64 mTracingController = TracingController.getInstance(); 65 Assert.assertNotNull(mTracingController); 66 } 67 68 @After tearDown()69 public void tearDown() throws Exception { 70 ensureTracingStopped(); 71 if (mSingleThreadExecutor != null) { 72 mSingleThreadExecutor.shutdown(); 73 if (!mSingleThreadExecutor.awaitTermination(EXECUTOR_TIMEOUT, TimeUnit.SECONDS)) { 74 Assert.fail("Failed to shutdown executor"); 75 } 76 } 77 78 if (mWebViewOnUiThread != null) { 79 mWebViewOnUiThread.cleanUp(); 80 } 81 } 82 83 /** 84 * This should remain functionally equivalent to 85 * android.webkit.cts.TracingControllerTest#testTracingControllerCallbacksOnUI. 86 * Modifications to this test should be reflected in that test as necessary. 87 * See http://go/modifying-webview-cts. 88 */ 89 @Test testTracingControllerCallbacksOnUI()90 public void testTracingControllerCallbacksOnUI() throws Throwable { 91 final TracingReceiver tracingReceiver = new TracingReceiver(); 92 WebkitUtils.onMainThreadSync( 93 () -> runTracingTestWithCallbacks(tracingReceiver, mSingleThreadExecutor)); 94 PollingCheck.check("Tracing did not complete", POLLING_TIMEOUT, 95 tracingReceiver.getCompleteCallable()); 96 assertThat(tracingReceiver.getNbChunks(), greaterThan(0)); 97 assertThat(tracingReceiver.getOutputStream().size(), greaterThan(0)); 98 } 99 100 /** 101 * This should remain functionally equivalent to 102 * android.webkit.cts.TracingControllerTest#testTracingControllerCallbacks. 103 * Modifications to this test should be reflected in that test as necessary. 104 * See http://go/modifying-webview-cts. 105 */ 106 @Test testTracingControllerCallbacks()107 public void testTracingControllerCallbacks() throws Throwable { 108 final TracingReceiver tracingReceiver = new TracingReceiver(); 109 runTracingTestWithCallbacks(tracingReceiver, mSingleThreadExecutor); 110 PollingCheck.check("Tracing did not complete", POLLING_TIMEOUT, 111 tracingReceiver.getCompleteCallable()); 112 assertThat(tracingReceiver.getNbChunks(), greaterThan(0)); 113 assertThat(tracingReceiver.getOutputStream().size(), greaterThan(0)); 114 } 115 116 /** 117 * This should remain functionally equivalent to 118 * android.webkit.cts.TracingControllerTest#testTracingStopFalseIfNotTracing. 119 * Modifications to this test should be reflected in that test as necessary. 120 * See http://go/modifying-webview-cts. 121 */ 122 @Test testTracingStopFalseIfNotTracing()123 public void testTracingStopFalseIfNotTracing() { 124 Assert.assertFalse(mTracingController.stop(null, mSingleThreadExecutor)); 125 Assert.assertFalse(mTracingController.isTracing()); 126 } 127 128 /** 129 * This should remain functionally equivalent to 130 * android.webkit.cts.TracingControllerTest#testTracingCannotStartIfAlreadyTracing. 131 * Modifications to this test should be reflected in that test as necessary. 132 * See http://go/modifying-webview-cts. 133 */ 134 @Test testTracingCannotStartIfAlreadyTracing()135 public void testTracingCannotStartIfAlreadyTracing() throws Exception { 136 TracingConfig config = new TracingConfig.Builder().build(); 137 mTracingController.start(config); 138 Assert.assertTrue(mTracingController.isTracing()); 139 try { 140 mTracingController.start(config); 141 } catch (IllegalStateException e) { 142 // as expected 143 return; 144 } 145 Assert.assertTrue(mTracingController.stop(null, mSingleThreadExecutor)); 146 Assert.fail("Tracing start should throw an exception " 147 + "when attempting to start while already tracing"); 148 } 149 150 /** 151 * This should remain functionally equivalent to 152 * android.webkit.cts.TracingControllerTest#testTracingInvalidCategoriesPatternExclusion. 153 * Modifications to this test should be reflected in that test as necessary. 154 * See http://go/modifying-webview-cts. 155 */ 156 @Test testTracingInvalidCategoriesPatternExclusion()157 public void testTracingInvalidCategoriesPatternExclusion() { 158 testInvalidCategoriesPattern(Arrays.asList("android_webview", "-blink")); 159 } 160 161 /** 162 * This should remain functionally equivalent to 163 * android.webkit.cts.TracingControllerTest#testTracingInvalidCategoriesPatternComma. 164 * Modifications to this test should be reflected in that test as necessary. 165 * See http://go/modifying-webview-cts. 166 */ 167 @Test testTracingInvalidCategoriesPatternComma()168 public void testTracingInvalidCategoriesPatternComma() { 169 testInvalidCategoriesPattern(Arrays.asList("android_webview, blink")); 170 } 171 172 @Test testIsSingleton()173 public void testIsSingleton() { 174 Assert.assertSame(TracingController.getInstance(), 175 TracingController.getInstance()); 176 } 177 testInvalidCategoriesPattern(List<String> categories)178 private void testInvalidCategoriesPattern(List<String> categories) { 179 try { 180 TracingConfig config = new TracingConfig.Builder() 181 .addCategories(categories) 182 .build(); 183 mTracingController.start(config); 184 } catch (IllegalArgumentException e) { 185 // as expected; 186 Assert.assertFalse(mTracingController.isTracing()); 187 return; 188 } 189 190 Assert.fail("Tracing start should throw an exception due to invalid category pattern"); 191 } 192 193 /** 194 * This should remain functionally equivalent to 195 * android.webkit.cts.TracingControllerTest#testTracingWithNullConfig. 196 * Modifications to this test should be reflected in that test as necessary. 197 * See http://go/modifying-webview-cts. 198 */ 199 @Test testTracingWithNullConfig()200 public void testTracingWithNullConfig() { 201 try { 202 mTracingController.start(null); 203 } catch (IllegalArgumentException e) { 204 // as expected 205 Assert.assertFalse(mTracingController.isTracing()); 206 return; 207 } 208 Assert.fail("Tracing start should throw exception if TracingConfig is null"); 209 } 210 runTracingTestWithCallbacks(TracingReceiver tracingReceiver, Executor executor)211 private void runTracingTestWithCallbacks(TracingReceiver tracingReceiver, Executor executor) { 212 Assert.assertNotNull(mTracingController); 213 214 TracingConfig config = new TracingConfig.Builder() 215 .addCategories(android.webkit.TracingConfig.CATEGORIES_WEB_DEVELOPER) 216 .setTracingMode(android.webkit.TracingConfig.RECORD_CONTINUOUSLY) 217 .build(); 218 mTracingController.start(config); 219 Assert.assertTrue(mTracingController.isTracing()); 220 221 mWebViewOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 222 Assert.assertTrue(mTracingController.stop(tracingReceiver, executor)); 223 } 224 ensureTracingStopped()225 private void ensureTracingStopped() throws Exception { 226 if (mTracingController == null) return; 227 mTracingController.stop(null, mSingleThreadExecutor); 228 Callable<Boolean> tracingStopped = () -> !mTracingController.isTracing(); 229 PollingCheck.check("Tracing did not stop", POLLING_TIMEOUT, tracingStopped); 230 } 231 getCustomThreadFactory()232 private ThreadFactory getCustomThreadFactory() { 233 return new ThreadFactory() { 234 private final AtomicInteger mThreadCount = new AtomicInteger(0); 235 236 @Override 237 public Thread newThread(Runnable r) { 238 Thread thread = new Thread(r); 239 thread.setName(EXECUTOR_THREAD_PREFIX + "_" + mThreadCount.incrementAndGet()); 240 return thread; 241 } 242 }; 243 } 244 245 /** 246 * This should remain functionally equivalent to 247 * android.webkit.cts.TracingControllerTest.TracingReceiver. 248 * Modifications to this test should be reflected in that test as necessary. 249 * See http://go/modifying-webview-cts. 250 */ 251 public static class TracingReceiver extends OutputStream { 252 private int mChunkCount; 253 private boolean mComplete; 254 private ByteArrayOutputStream mOutputStream; 255 TracingReceiver()256 public TracingReceiver() { 257 mOutputStream = new ByteArrayOutputStream(); 258 } 259 260 @Override write(byte[] chunk)261 public void write(byte[] chunk) { 262 validateThread(); 263 mChunkCount++; 264 try { 265 mOutputStream.write(chunk); 266 } catch (IOException e) { 267 throw new RuntimeException(e); 268 } 269 } 270 271 @Override close()272 public void close() { 273 validateThread(); 274 mComplete = true; 275 } 276 277 @Override flush()278 public void flush() { 279 Assert.fail("flush should not be called"); 280 } 281 282 @Override write(int b)283 public void write(int b) { 284 Assert.fail("write(int) should not be called"); 285 } 286 287 @Override write(byte[] b, int off, int len)288 public void write(byte[] b, int off, int len) { 289 Assert.fail("write(byte[], int, int) should not be called"); 290 } 291 validateThread()292 private void validateThread() { 293 Assert.assertTrue(Thread.currentThread().getName().startsWith(EXECUTOR_THREAD_PREFIX)); 294 } 295 getNbChunks()296 int getNbChunks() { 297 return mChunkCount; 298 299 } 300 getComplete()301 boolean getComplete() { 302 return mComplete; 303 } 304 getCompleteCallable()305 Callable<Boolean> getCompleteCallable() { 306 return this::getComplete; 307 } 308 getOutputStream()309 ByteArrayOutputStream getOutputStream() { 310 return mOutputStream; 311 } 312 } 313 } 314