• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net.impl;
6 
7 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
8 import static android.os.Process.THREAD_PRIORITY_DEFAULT;
9 
10 import static com.google.common.truth.Truth.assertThat;
11 
12 import static org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP;
13 
14 import android.content.Context;
15 import android.os.Build;
16 import android.os.ConditionVariable;
17 
18 import androidx.test.filters.SmallTest;
19 
20 import org.json.JSONException;
21 import org.json.JSONObject;
22 import org.junit.After;
23 import org.junit.Before;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.junit.rules.RuleChain;
27 import org.junit.runner.RunWith;
28 import org.junit.runners.JUnit4;
29 
30 import org.chromium.base.test.util.Batch;
31 import org.chromium.net.CronetEngine;
32 import org.chromium.net.CronetLoggerTestRule;
33 import org.chromium.net.CronetTestRule;
34 import org.chromium.net.CronetTestRule.CronetImplementation;
35 import org.chromium.net.CronetTestRule.IgnoreFor;
36 import org.chromium.net.CronetTestRule.RequiresMinAndroidApi;
37 import org.chromium.net.ExperimentalCronetEngine;
38 import org.chromium.net.NativeTestServer;
39 import org.chromium.net.TestUrlRequestCallback;
40 import org.chromium.net.UrlRequest;
41 import org.chromium.net.impl.CronetEngineBuilderImpl.HttpCacheMode;
42 import org.chromium.net.impl.CronetLogger.CronetEngineBuilderInfo;
43 import org.chromium.net.impl.CronetLogger.CronetSource;
44 import org.chromium.net.impl.CronetLogger.CronetTrafficInfo;
45 import org.chromium.net.impl.CronetLogger.CronetVersion;
46 
47 import java.time.Duration;
48 import java.util.AbstractMap;
49 import java.util.Arrays;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Locale;
54 import java.util.Map;
55 import java.util.concurrent.atomic.AtomicInteger;
56 import java.util.concurrent.atomic.AtomicReference;
57 
58 /** Test logging functionalities. */
59 @Batch(Batch.UNIT_TESTS)
60 @RunWith(JUnit4.class)
61 @RequiresMinAndroidApi(Build.VERSION_CODES.O)
62 @IgnoreFor(
63         implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
64         reason = "CronetLoggerTestRule is supported only by the native implementation.")
65 public final class CronetLoggerTest {
66     private final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup();
67     private final CronetLoggerTestRule mLoggerTestRule = new CronetLoggerTestRule(TestLogger.class);
68 
69     @Rule public final RuleChain chain = RuleChain.outerRule(mTestRule).around(mLoggerTestRule);
70 
71     private TestLogger mTestLogger;
72     private Context mContext;
73 
74     @Before
setUp()75     public void setUp() {
76         mContext = mTestRule.getTestFramework().getContext();
77         mTestLogger = (TestLogger) mLoggerTestRule.mTestLogger;
78         assertThat(NativeTestServer.startNativeTestServer(mContext)).isTrue();
79     }
80 
81     @After
tearDown()82     public void tearDown() {
83         mTestLogger = null;
84         NativeTestServer.shutdownNativeTestServer();
85     }
86 
87     @Test
88     @SmallTest
testCronetEngineInfoCreation()89     public void testCronetEngineInfoCreation() {
90         CronetEngineBuilderImpl builder = new NativeCronetEngineBuilderImpl(mContext);
91         CronetEngineBuilderInfo builderInfo = new CronetEngineBuilderInfo(builder);
92         assertThat(builderInfo.isPublicKeyPinningBypassForLocalTrustAnchorsEnabled())
93                 .isEqualTo(builder.publicKeyPinningBypassForLocalTrustAnchorsEnabled());
94         assertThat(builderInfo.getUserAgent()).isEqualTo(builder.getUserAgent());
95         assertThat(builderInfo.getStoragePath()).isEqualTo(builder.storagePath());
96         assertThat(builderInfo.isQuicEnabled()).isEqualTo(builder.quicEnabled());
97         assertThat(builderInfo.isHttp2Enabled()).isEqualTo(builder.http2Enabled());
98         assertThat(builderInfo.isBrotliEnabled()).isEqualTo(builder.brotliEnabled());
99         assertThat(builderInfo.getHttpCacheMode()).isEqualTo(builder.publicBuilderHttpCacheMode());
100         assertThat(builderInfo.getExperimentalOptions()).isEqualTo(builder.experimentalOptions());
101         assertThat(builderInfo.isNetworkQualityEstimatorEnabled())
102                 .isEqualTo(builder.networkQualityEstimatorEnabled());
103         assertThat(builderInfo.getThreadPriority())
104                 .isEqualTo(builder.threadPriority(THREAD_PRIORITY_BACKGROUND));
105     }
106 
107     @Test
108     @SmallTest
testCronetVersionCreation()109     public void testCronetVersionCreation() {
110         final int major = 100;
111         final int minor = 0;
112         final int build = 1;
113         final int patch = 33;
114         final String version = String.format(Locale.US, "%d.%d.%d.%d", major, minor, build, patch);
115         final CronetVersion parsedVersion = new CronetVersion(version);
116         assertThat(parsedVersion.getMajorVersion()).isEqualTo(major);
117         assertThat(parsedVersion.getMinorVersion()).isEqualTo(minor);
118         assertThat(parsedVersion.getBuildVersion()).isEqualTo(build);
119         assertThat(parsedVersion.getPatchVersion()).isEqualTo(patch);
120     }
121 
122     @Test
123     @SmallTest
testHttpCacheModeEnum()124     public void testHttpCacheModeEnum() {
125         final int[] publicBuilderHttpCacheModes = {
126             CronetEngine.Builder.HTTP_CACHE_DISABLED,
127             CronetEngine.Builder.HTTP_CACHE_IN_MEMORY,
128             CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP,
129             CronetEngine.Builder.HTTP_CACHE_DISK
130         };
131         for (int publicBuilderHttpCacheMode : publicBuilderHttpCacheModes) {
132             HttpCacheMode cacheModeEnum =
133                     HttpCacheMode.fromPublicBuilderCacheMode(publicBuilderHttpCacheMode);
134             assertThat(cacheModeEnum.toPublicBuilderCacheMode())
135                     .isEqualTo(publicBuilderHttpCacheMode);
136         }
137     }
138 
139     @Test
140     @SmallTest
testSetLoggerForTesting()141     public void testSetLoggerForTesting() {
142         CronetLogger logger = CronetLoggerFactory.createLogger(mContext, null);
143         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(0);
144         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(0);
145 
146         // We don't care about what's being logged.
147         logger.logCronetTrafficInfo(0, null);
148         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(1);
149         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(0);
150         logger.logCronetEngineCreation(0, null, null, null);
151         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(1);
152         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(1);
153     }
154 
155     @Test
156     @SmallTest
testTelemetryDefaultEnabled()157     public void testTelemetryDefaultEnabled() throws JSONException {
158         final String url = NativeTestServer.getEchoBodyURL();
159 
160         TestUrlRequestCallback callback = new TestUrlRequestCallback();
161         CronetEngine engine = mTestRule.getTestFramework().startEngine();
162         UrlRequest.Builder requestBuilder =
163                 engine.newUrlRequestBuilder(url, callback, callback.getExecutor());
164         UrlRequest request = requestBuilder.build();
165         request.start();
166         callback.blockForDone();
167         assertThat(callback.mOnCanceledCalled).isFalse();
168         assertThat(callback.mOnErrorCalled).isFalse();
169         mTestLogger.waitForLogCronetTrafficInfo();
170 
171         // Test-logger should be bypassed.
172         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(1);
173         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(1);
174     }
175 
176     @Test
177     @SmallTest
testTelemetryDisabled()178     public void testTelemetryDisabled() throws JSONException {
179         final String url = NativeTestServer.getEchoBodyURL();
180         JSONObject jsonExperimentalOptions = new JSONObject().put("enable_telemetry", false);
181         final String experimentalOptions = jsonExperimentalOptions.toString();
182         mTestRule
183                 .getTestFramework()
184                 .applyEngineBuilderPatch(
185                         (builder) -> builder.setExperimentalOptions(experimentalOptions));
186         CronetEngine engine = mTestRule.getTestFramework().startEngine();
187         TestUrlRequestCallback callback = new TestUrlRequestCallback();
188         UrlRequest.Builder requestBuilder =
189                 engine.newUrlRequestBuilder(url, callback, callback.getExecutor());
190         UrlRequest request = requestBuilder.build();
191         request.start();
192         callback.blockForDone();
193 
194         // Test-logger should be bypassed.
195         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(0);
196         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(0);
197     }
198 
199     @Test
200     @SmallTest
testEngineCreation()201     public void testEngineCreation() throws JSONException {
202         JSONObject staleDns =
203                 new JSONObject()
204                         .put("enable", true)
205                         .put("delay_ms", 0)
206                         .put("allow_other_network", true)
207                         .put("persist_to_disk", true)
208                         .put("persist_delay_ms", 0);
209         final JSONObject jsonExperimentalOptions = new JSONObject().put("StaleDNS", staleDns);
210         final String experimentalOptions = jsonExperimentalOptions.toString();
211         final boolean isPublicKeyPinningBypassForLocalTrustAnchorsEnabled = false;
212         final String userAgent = "myUserAgent";
213         final String storagePath = CronetTestRule.getTestStorage(mContext);
214         final boolean isQuicEnabled = true;
215         final boolean isHttp2Enabled = false;
216         final boolean isBrotliEnabled = true;
217         final int cacheMode = HTTP_CACHE_DISK_NO_HTTP;
218         final boolean isNetworkQualityEstimatorEnabled = true;
219         final int threadPriority = THREAD_PRIORITY_DEFAULT;
220 
221         mTestRule
222                 .getTestFramework()
223                 .applyEngineBuilderPatch(
224                         (builder) -> {
225                             builder.setExperimentalOptions(experimentalOptions);
226                             builder.enablePublicKeyPinningBypassForLocalTrustAnchors(
227                                     isPublicKeyPinningBypassForLocalTrustAnchorsEnabled);
228                             builder.setUserAgent(userAgent);
229                             builder.setStoragePath(storagePath);
230                             builder.enableQuic(isQuicEnabled);
231                             builder.enableHttp2(isHttp2Enabled);
232                             builder.enableBrotli(isBrotliEnabled);
233                             builder.enableHttpCache(cacheMode, 0);
234                             builder.enableNetworkQualityEstimator(isNetworkQualityEstimatorEnabled);
235                             builder.setThreadPriority(threadPriority);
236                         });
237 
238         CronetEngine engine = mTestRule.getTestFramework().startEngine();
239         final CronetEngineBuilderInfo builderInfo = mTestLogger.getLastCronetEngineBuilderInfo();
240         final CronetVersion version = mTestLogger.getLastCronetVersion();
241         final CronetSource source = mTestLogger.getLastCronetSource();
242 
243         assertThat(builderInfo.isPublicKeyPinningBypassForLocalTrustAnchorsEnabled())
244                 .isEqualTo(isPublicKeyPinningBypassForLocalTrustAnchorsEnabled);
245         assertThat(builderInfo.getUserAgent()).isEqualTo(userAgent);
246         assertThat(builderInfo.getStoragePath()).isEqualTo(storagePath);
247         assertThat(builderInfo.isQuicEnabled()).isEqualTo(isQuicEnabled);
248         assertThat(builderInfo.isHttp2Enabled()).isEqualTo(isHttp2Enabled);
249         assertThat(builderInfo.isBrotliEnabled()).isEqualTo(isBrotliEnabled);
250         assertThat(builderInfo.getHttpCacheMode()).isEqualTo(cacheMode);
251         assertThat(builderInfo.getExperimentalOptions()).isEqualTo(experimentalOptions);
252         assertThat(builderInfo.isNetworkQualityEstimatorEnabled())
253                 .isEqualTo(isNetworkQualityEstimatorEnabled);
254         assertThat(builderInfo.getThreadPriority()).isEqualTo(threadPriority);
255         assertThat(version.toString()).isEqualTo(ImplVersion.getCronetVersion());
256         if (mTestRule.testingJavaImpl()) {
257             assertThat(source).isEqualTo(CronetSource.CRONET_SOURCE_FALLBACK);
258         } else {
259             assertThat(source).isEqualTo(CronetSource.CRONET_SOURCE_STATICALLY_LINKED);
260         }
261 
262         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(1);
263         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(0);
264     }
265 
266     @Test
267     @SmallTest
testEngineCreationAndTrafficInfoEngineId()268     public void testEngineCreationAndTrafficInfoEngineId() throws Exception {
269         final String url = "www.example.com";
270         CronetEngine engine = mTestRule.getTestFramework().startEngine();
271         final int engineId = mTestLogger.getLastCronetEngineId();
272 
273         TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
274         UrlRequest.Builder requestBuilder1 =
275                 engine.newUrlRequestBuilder(url, callback1, callback1.getExecutor());
276         UrlRequest request1 = requestBuilder1.build();
277         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
278         UrlRequest.Builder requestBuilder2 =
279                 engine.newUrlRequestBuilder(url, callback2, callback2.getExecutor());
280         UrlRequest request2 = requestBuilder2.build();
281 
282         request1.start();
283         callback1.blockForDone();
284         mTestLogger.waitForLogCronetTrafficInfo();
285         final int request1Id = mTestLogger.getLastCronetRequestId();
286 
287         request2.start();
288         callback2.blockForDone();
289         mTestLogger.waitForLogCronetTrafficInfo();
290         final int request2Id = mTestLogger.getLastCronetRequestId();
291 
292         assertThat(request1Id).isEqualTo(engineId);
293         assertThat(request2Id).isEqualTo(engineId);
294 
295         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(1);
296         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(2);
297     }
298 
299     @Test
300     @SmallTest
testMultipleEngineCreationAndTrafficInfoEngineId()301     public void testMultipleEngineCreationAndTrafficInfoEngineId() throws Exception {
302         final String url = "www.example.com";
303         ExperimentalCronetEngine.Builder engineBuilder =
304                 (ExperimentalCronetEngine.Builder)
305                         mTestRule
306                                 .getTestFramework()
307                                 .createNewSecondaryBuilder(
308                                         mTestRule.getTestFramework().getContext());
309 
310         CronetEngine engine1 = engineBuilder.build();
311         final int engine1Id = mTestLogger.getLastCronetEngineId();
312         CronetEngine engine2 = engineBuilder.build();
313         final int engine2Id = mTestLogger.getLastCronetEngineId();
314 
315         try {
316             TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
317             UrlRequest.Builder requestBuilder1 =
318                     engine1.newUrlRequestBuilder(url, callback1, callback1.getExecutor());
319             UrlRequest request1 = requestBuilder1.build();
320             TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
321             UrlRequest.Builder requestBuilder2 =
322                     engine2.newUrlRequestBuilder(url, callback2, callback2.getExecutor());
323             UrlRequest request2 = requestBuilder2.build();
324 
325             request1.start();
326             callback1.blockForDone();
327             mTestLogger.waitForLogCronetTrafficInfo();
328             final int request1Id = mTestLogger.getLastCronetRequestId();
329 
330             request2.start();
331             callback2.blockForDone();
332             mTestLogger.waitForLogCronetTrafficInfo();
333             final int request2Id = mTestLogger.getLastCronetRequestId();
334 
335             assertThat(request1Id).isEqualTo(engine1Id);
336             assertThat(request2Id).isEqualTo(engine2Id);
337 
338             assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(2);
339             assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(2);
340         } finally {
341             engine1.shutdown();
342             engine2.shutdown();
343         }
344     }
345 
346     @Test
347     @SmallTest
testSuccessfulRequestNative()348     public void testSuccessfulRequestNative() throws Exception {
349         final String url = NativeTestServer.getEchoBodyURL();
350         CronetEngine engine = mTestRule.getTestFramework().startEngine();
351 
352         TestUrlRequestCallback callback = new TestUrlRequestCallback();
353         UrlRequest.Builder requestBuilder =
354                 engine.newUrlRequestBuilder(url, callback, callback.getExecutor());
355         UrlRequest request = requestBuilder.build();
356         request.start();
357         callback.blockForDone();
358         assertThat(callback.mOnCanceledCalled).isFalse();
359         assertThat(callback.mOnErrorCalled).isFalse();
360         mTestLogger.waitForLogCronetTrafficInfo();
361 
362         final CronetTrafficInfo trafficInfo = mTestLogger.getLastCronetTrafficInfo();
363         assertThat(trafficInfo.getRequestHeaderSizeInBytes()).isEqualTo(0);
364         assertThat(trafficInfo.getRequestBodySizeInBytes()).isNotEqualTo(0);
365         assertThat(trafficInfo.getResponseHeaderSizeInBytes()).isNotEqualTo(0);
366         assertThat(trafficInfo.getResponseBodySizeInBytes()).isNotEqualTo(0);
367         assertThat(trafficInfo.getResponseStatusCode()).isEqualTo(200);
368         assertThat(trafficInfo.getHeadersLatency()).isNotEqualTo(Duration.ofSeconds(0));
369         assertThat(trafficInfo.getTotalLatency()).isNotEqualTo(Duration.ofSeconds(0));
370         assertThat(trafficInfo.getNegotiatedProtocol()).isNotNull();
371         assertThat(trafficInfo.wasConnectionMigrationAttempted()).isFalse();
372         assertThat(trafficInfo.didConnectionMigrationSucceed()).isFalse();
373 
374         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(1);
375         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(1);
376     }
377 
378     @Test
379     @SmallTest
testFailedRequestNative()380     public void testFailedRequestNative() throws Exception {
381         final String url = "www.unreachable-url.com";
382         CronetEngine engine = mTestRule.getTestFramework().startEngine();
383 
384         TestUrlRequestCallback callback = new TestUrlRequestCallback();
385         UrlRequest.Builder requestBuilder =
386                 engine.newUrlRequestBuilder(url, callback, callback.getExecutor());
387         UrlRequest request = requestBuilder.build();
388         request.start();
389         callback.blockForDone();
390         assertThat(callback.mOnCanceledCalled).isFalse();
391         assertThat(callback.mOnErrorCalled).isTrue();
392         mTestLogger.waitForLogCronetTrafficInfo();
393 
394         final CronetTrafficInfo trafficInfo = mTestLogger.getLastCronetTrafficInfo();
395         assertThat(trafficInfo.getRequestHeaderSizeInBytes()).isEqualTo(0);
396         assertThat(trafficInfo.getRequestBodySizeInBytes()).isEqualTo(0);
397         assertThat(trafficInfo.getResponseHeaderSizeInBytes()).isEqualTo(0);
398         assertThat(trafficInfo.getResponseBodySizeInBytes()).isEqualTo(0);
399         // When a request fails before hitting the server all these values won't be populated in
400         // the actual code. Check that the logger sets them to some known defaults before
401         // logging.
402         assertThat(trafficInfo.getResponseStatusCode()).isEqualTo(0);
403         assertThat(trafficInfo.getNegotiatedProtocol()).isEmpty();
404         assertThat(trafficInfo.wasConnectionMigrationAttempted()).isFalse();
405         assertThat(trafficInfo.didConnectionMigrationSucceed()).isFalse();
406 
407         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(1);
408         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(1);
409     }
410 
411     @Test
412     @SmallTest
testCanceledRequestNative()413     public void testCanceledRequestNative() throws Exception {
414         final String url = NativeTestServer.getEchoBodyURL();
415         CronetEngine engine = mTestRule.getTestFramework().startEngine();
416 
417         TestUrlRequestCallback callback = new TestUrlRequestCallback();
418         callback.setAutoAdvance(false);
419         UrlRequest.Builder requestBuilder =
420                 engine.newUrlRequestBuilder(url, callback, callback.getExecutor());
421         UrlRequest request = requestBuilder.build();
422         request.start();
423         request.cancel();
424         callback.blockForDone();
425         assertThat(callback.mOnCanceledCalled).isTrue();
426         assertThat(callback.mOnErrorCalled).isFalse();
427         mTestLogger.waitForLogCronetTrafficInfo();
428 
429         final CronetTrafficInfo trafficInfo = mTestLogger.getLastCronetTrafficInfo();
430         assertThat(trafficInfo.getRequestHeaderSizeInBytes()).isEqualTo(0);
431         assertThat(trafficInfo.getRequestBodySizeInBytes()).isEqualTo(0);
432         assertThat(trafficInfo.getResponseHeaderSizeInBytes()).isEqualTo(0);
433         assertThat(trafficInfo.getResponseBodySizeInBytes()).isEqualTo(0);
434         // When a request fails before hitting the server all these values won't be populated in
435         // the actual code. Check that the logger sets them to some known defaults before
436         // logging.
437         assertThat(trafficInfo.getResponseStatusCode()).isEqualTo(0);
438         assertThat(trafficInfo.getNegotiatedProtocol()).isEmpty();
439         assertThat(trafficInfo.wasConnectionMigrationAttempted()).isFalse();
440         assertThat(trafficInfo.didConnectionMigrationSucceed()).isFalse();
441 
442         assertThat(mTestLogger.callsToLogCronetEngineCreation()).isEqualTo(1);
443         assertThat(mTestLogger.callsToLogCronetTrafficInfo()).isEqualTo(1);
444     }
445 
446     @Test
447     @SmallTest
testEmptyHeadersSizeNative()448     public void testEmptyHeadersSizeNative() {
449         Map<String, List<String>> headers = Collections.emptyMap();
450         assertThat(CronetUrlRequest.estimateHeadersSizeInBytes(headers)).isEqualTo(0);
451         headers = null;
452         assertThat(CronetUrlRequest.estimateHeadersSizeInBytes(headers)).isEqualTo(0);
453 
454         CronetUrlRequest.HeadersList headersList = new CronetUrlRequest.HeadersList();
455         assertThat(CronetUrlRequest.estimateHeadersSizeInBytes(headersList)).isEqualTo(0);
456         headersList = null;
457         assertThat(CronetUrlRequest.estimateHeadersSizeInBytes(headersList)).isEqualTo(0);
458     }
459 
460     @Test
461     @SmallTest
testNonEmptyHeadersSizeNative()462     public void testNonEmptyHeadersSizeNative() {
463         Map<String, List<String>> headers =
464                 new HashMap<String, List<String>>() {
465                     {
466                         put("header1", Arrays.asList("value1", "value2")); // 7 + 6 + 6 = 19
467                         put("header2", null); // 19 + 7 = 26
468                         put("header3", Collections.emptyList()); // 26 + 7 + 0 = 33
469                         put(null, Arrays.asList("")); // 33 + 0 + 0 = 33
470                     }
471                 };
472         assertThat(CronetUrlRequest.estimateHeadersSizeInBytes(headers)).isEqualTo(33);
473 
474         CronetUrlRequest.HeadersList headersList = new CronetUrlRequest.HeadersList();
475         headersList.add(
476                 new AbstractMap.SimpleImmutableEntry<String, String>(
477                         "header1", "value1") // 7 + 6 = 13
478                 );
479         headersList.add(
480                 new AbstractMap.SimpleImmutableEntry<String, String>(
481                         "header1", "value2") // 13 + 7 + 6 = 26
482                 );
483         headersList.add(
484                 new AbstractMap.SimpleImmutableEntry<String, String>(
485                         "header2", null) // 26 + 7 + 0 = 33
486                 );
487         headersList.add(
488                 new AbstractMap.SimpleImmutableEntry<String, String>(null, "") // 33 + 0 + 0 = 33
489                 );
490         assertThat(CronetUrlRequest.estimateHeadersSizeInBytes(headersList)).isEqualTo(33);
491     }
492 
493     /** Records the last engine creation (and traffic info) call it has received. */
494     public static final class TestLogger extends CronetLogger {
495         private AtomicInteger mCallsToLogCronetEngineCreation = new AtomicInteger();
496         private AtomicInteger mCallsToLogCronetTrafficInfo = new AtomicInteger();
497         private AtomicInteger mCronetEngineId = new AtomicInteger();
498         private AtomicInteger mCronetRequestId = new AtomicInteger();
499         private AtomicReference<CronetTrafficInfo> mTrafficInfo = new AtomicReference<>();
500         private AtomicReference<CronetEngineBuilderInfo> mBuilderInfo = new AtomicReference<>();
501         private AtomicReference<CronetVersion> mVersion = new AtomicReference<>();
502         private AtomicReference<CronetSource> mSource = new AtomicReference<>();
503         private final ConditionVariable mBlock = new ConditionVariable();
504 
505         @Override
logCronetEngineCreation( int cronetEngineId, CronetEngineBuilderInfo engineBuilderInfo, CronetVersion version, CronetSource source)506         public void logCronetEngineCreation(
507                 int cronetEngineId,
508                 CronetEngineBuilderInfo engineBuilderInfo,
509                 CronetVersion version,
510                 CronetSource source) {
511             mCallsToLogCronetEngineCreation.incrementAndGet();
512             mCronetEngineId.set(cronetEngineId);
513             mBuilderInfo.set(engineBuilderInfo);
514             mVersion.set(version);
515             mSource.set(source);
516         }
517 
518         @Override
logCronetTrafficInfo(int cronetEngineId, CronetTrafficInfo trafficInfo)519         public void logCronetTrafficInfo(int cronetEngineId, CronetTrafficInfo trafficInfo) {
520             mCallsToLogCronetTrafficInfo.incrementAndGet();
521             mCronetRequestId.set(cronetEngineId);
522             mTrafficInfo.set(trafficInfo);
523             mBlock.open();
524         }
525 
callsToLogCronetTrafficInfo()526         public int callsToLogCronetTrafficInfo() {
527             return mCallsToLogCronetTrafficInfo.get();
528         }
529 
callsToLogCronetEngineCreation()530         public int callsToLogCronetEngineCreation() {
531             return mCallsToLogCronetEngineCreation.get();
532         }
533 
waitForLogCronetTrafficInfo()534         public void waitForLogCronetTrafficInfo() {
535             mBlock.block();
536             mBlock.close();
537         }
538 
getLastCronetEngineId()539         public int getLastCronetEngineId() {
540             return mCronetEngineId.get();
541         }
542 
getLastCronetRequestId()543         public int getLastCronetRequestId() {
544             return mCronetRequestId.get();
545         }
546 
getLastCronetTrafficInfo()547         public CronetTrafficInfo getLastCronetTrafficInfo() {
548             return mTrafficInfo.get();
549         }
550 
getLastCronetEngineBuilderInfo()551         public CronetEngineBuilderInfo getLastCronetEngineBuilderInfo() {
552             return mBuilderInfo.get();
553         }
554 
getLastCronetVersion()555         public CronetVersion getLastCronetVersion() {
556             return mVersion.get();
557         }
558 
getLastCronetSource()559         public CronetSource getLastCronetSource() {
560             return mSource.get();
561         }
562     }
563 }
564