• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 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;
6 
7 import static com.google.common.truth.Truth.assertThat;
8 import static com.google.common.truth.Truth.assertWithMessage;
9 
10 import static org.chromium.net.CronetTestRule.getTestStorage;
11 import static org.chromium.net.truth.UrlResponseInfoSubject.assertThat;
12 
13 import androidx.test.ext.junit.runners.AndroidJUnit4;
14 import androidx.test.filters.LargeTest;
15 import androidx.test.filters.SmallTest;
16 import com.android.testutils.SkipPresubmit;
17 
18 import org.json.JSONObject;
19 import org.junit.After;
20 import org.junit.Before;
21 import org.junit.Rule;
22 import org.junit.Test;
23 import org.junit.runner.RunWith;
24 
25 import org.chromium.base.test.util.DoNotBatch;
26 import org.chromium.net.CronetTestRule.CronetImplementation;
27 import org.chromium.net.CronetTestRule.IgnoreFor;
28 
29 import java.io.File;
30 import java.io.FileInputStream;
31 import java.io.IOException;
32 import java.util.Date;
33 import java.util.concurrent.Executors;
34 
35 /** Tests making requests using QUIC. */
36 @DoNotBatch(reason = "crbug/1459563")
37 @RunWith(AndroidJUnit4.class)
38 @IgnoreFor(
39         implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
40         reason =
41                 "The fallback implementation doesn't support QUIC. "
42                         + "crbug.com/1494870: Enable for AOSP_PLATFORM once fixed")
43 public class QuicTest {
44     @Rule public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup();
45 
46     @Before
setUp()47     public void setUp() throws Exception {
48         // Load library first, since we need the Quic test server's URL.
49         System.loadLibrary("cronet_tests");
50         QuicTestServer.startQuicTestServer(mTestRule.getTestFramework().getContext());
51 
52         mTestRule
53                 .getTestFramework()
54                 .applyEngineBuilderPatch(
55                         (builder) -> {
56                             builder.enableNetworkQualityEstimator(true).enableQuic(true);
57                             builder.addQuicHint(
58                                     QuicTestServer.getServerHost(),
59                                     QuicTestServer.getServerPort(),
60                                     QuicTestServer.getServerPort());
61 
62                             // The pref may not be written if the computed Effective Connection Type
63                             // (ECT) matches the default ECT for the current connection type.
64                             // Force the ECT to "Slow-2G". Since "Slow-2G" is not the default ECT
65                             // for any connection type, this ensures that the pref is written to.
66                             JSONObject nqeParams =
67                                     new JSONObject()
68                                             .put("force_effective_connection_type", "Slow-2G");
69 
70                             // TODO(mgersh): Enable connection migration once it works, see
71                             // http://crbug.com/634910
72                             JSONObject quicParams =
73                                     new JSONObject()
74                                             .put("connection_options", "PACE,IW10,FOO,DEADBEEF")
75                                             .put("max_server_configs_stored_in_properties", 2)
76                                             .put("idle_connection_timeout_seconds", 300)
77                                             .put("migrate_sessions_on_network_change_v2", false)
78                                             .put("migrate_sessions_early_v2", false)
79                                             .put("race_cert_verification", true);
80                             JSONObject hostResolverParams =
81                                     CronetTestUtil.generateHostResolverRules();
82                             JSONObject experimentalOptions =
83                                     new JSONObject()
84                                             .put("QUIC", quicParams)
85                                             .put("HostResolverRules", hostResolverParams)
86                                             .put("NetworkQualityEstimator", nqeParams);
87                             builder.setExperimentalOptions(experimentalOptions.toString());
88                             builder.setStoragePath(
89                                     getTestStorage(mTestRule.getTestFramework().getContext()));
90                             builder.enableHttpCache(
91                                     CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 1000 * 1024);
92                             CronetTestUtil.setMockCertVerifierForTesting(
93                                     builder, QuicTestServer.createMockCertVerifier());
94                         });
95         mTestRule.getTestFramework().startEngine();
96     }
97 
98     @After
tearDown()99     public void tearDown() throws Exception {
100         QuicTestServer.shutdownQuicTestServer();
101     }
102 
103     @Test
104     @LargeTest
testQuicLoadUrl()105     public void testQuicLoadUrl() throws Exception {
106         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().getEngine();
107         String quicURL = QuicTestServer.getServerURL() + "/simple.txt";
108         TestUrlRequestCallback callback = new TestUrlRequestCallback();
109 
110         // Although the native stack races QUIC and SPDY for the first request,
111         // since there is no http server running on the corresponding TCP port,
112         // QUIC will always succeed with a 200 (see
113         // net::HttpStreamFactoryImpl::Request::OnStreamFailed).
114         UrlRequest.Builder requestBuilder =
115                 cronetEngine.newUrlRequestBuilder(quicURL, callback, callback.getExecutor());
116         requestBuilder.build().start();
117         callback.blockForDone();
118 
119         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
120         String expectedContent = "This is a simple text file served by QUIC.\n";
121         assertThat(callback.mResponseAsString).isEqualTo(expectedContent);
122         assertIsQuic(callback.getResponseInfoWithChecks());
123         // The total received bytes should be larger than the content length, to account for
124         // headers.
125         assertThat(callback.getResponseInfoWithChecks())
126                 .hasReceivedByteCountThat()
127                 .isGreaterThan((long) expectedContent.length());
128         CronetTestUtil.nativeFlushWritePropertiesForTesting(cronetEngine);
129         assertThat(
130                         fileContainsString(
131                                 "local_prefs.json",
132                                 QuicTestServer.getServerHost()
133                                         + ":"
134                                         + QuicTestServer.getServerPort()))
135                 .isTrue();
136         cronetEngine.shutdown();
137 
138         // Make another request using a new context but with no QUIC hints.
139         ExperimentalCronetEngine.Builder builder =
140                 new ExperimentalCronetEngine.Builder(mTestRule.getTestFramework().getContext());
141         builder.setStoragePath(getTestStorage(mTestRule.getTestFramework().getContext()));
142         builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, 1000 * 1024);
143         builder.enableQuic(true);
144         JSONObject hostResolverParams = CronetTestUtil.generateHostResolverRules();
145         JSONObject experimentalOptions =
146                 new JSONObject().put("HostResolverRules", hostResolverParams);
147         builder.setExperimentalOptions(experimentalOptions.toString());
148         CronetTestUtil.setMockCertVerifierForTesting(
149                 builder, QuicTestServer.createMockCertVerifier());
150         cronetEngine = builder.build();
151         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
152         requestBuilder =
153                 cronetEngine.newUrlRequestBuilder(quicURL, callback2, callback2.getExecutor());
154         requestBuilder.build().start();
155         callback2.blockForDone();
156         assertThat(callback2.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
157         assertThat(callback2.mResponseAsString).isEqualTo(expectedContent);
158         assertIsQuic(callback.getResponseInfoWithChecks());
159         // The total received bytes should be larger than the content length, to account for
160         // headers.
161         assertThat(callback2.getResponseInfoWithChecks())
162                 .hasReceivedByteCountThat()
163                 .isGreaterThan((long) expectedContent.length());
164         cronetEngine.shutdown();
165     }
166 
167     // Returns whether a file contains a particular string.
fileContainsString(String filename, String content)168     private boolean fileContainsString(String filename, String content) throws IOException {
169         File file =
170                 new File(
171                         getTestStorage(mTestRule.getTestFramework().getContext())
172                                 + "/prefs/"
173                                 + filename);
174         FileInputStream fileInputStream = new FileInputStream(file);
175         byte[] data = new byte[(int) file.length()];
176         fileInputStream.read(data);
177         fileInputStream.close();
178         return new String(data, "UTF-8").contains(content);
179     }
180 
181     /** Tests that the network quality listeners are propoerly notified when QUIC is enabled. */
182     @Test
183     @LargeTest
184     @SuppressWarnings("deprecation")
testNQEWithQuic()185     public void testNQEWithQuic() throws Exception {
186         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().getEngine();
187         String quicURL = QuicTestServer.getServerURL() + "/simple.txt";
188 
189         TestNetworkQualityRttListener rttListener =
190                 new TestNetworkQualityRttListener(Executors.newSingleThreadExecutor());
191         TestNetworkQualityThroughputListener throughputListener =
192                 new TestNetworkQualityThroughputListener(Executors.newSingleThreadExecutor());
193 
194         cronetEngine.addRttListener(rttListener);
195         cronetEngine.addThroughputListener(throughputListener);
196 
197         cronetEngine.configureNetworkQualityEstimatorForTesting(true, true, true);
198         TestUrlRequestCallback callback = new TestUrlRequestCallback();
199 
200         // Although the native stack races QUIC and SPDY for the first request,
201         // since there is no http server running on the corresponding TCP port,
202         // QUIC will always succeed with a 200 (see
203         // net::HttpStreamFactoryImpl::Request::OnStreamFailed).
204         UrlRequest.Builder requestBuilder =
205                 cronetEngine.newUrlRequestBuilder(quicURL, callback, callback.getExecutor());
206         requestBuilder.build().start();
207         callback.blockForDone();
208 
209         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
210         String expectedContent = "This is a simple text file served by QUIC.\n";
211         assertThat(callback.mResponseAsString).isEqualTo(expectedContent);
212         assertIsQuic(callback.getResponseInfoWithChecks());
213 
214         // Throughput observation is posted to the network quality estimator on the network thread
215         // after the UrlRequest is completed. The observations are then eventually posted to
216         // throughput listeners on the executor provided to network quality.
217         throughputListener.waitUntilFirstThroughputObservationReceived();
218 
219         // Wait for RTT observation (at the URL request layer) to be posted.
220         rttListener.waitUntilFirstUrlRequestRTTReceived();
221 
222         assertThat(throughputListener.throughputObservationCount()).isGreaterThan(0);
223 
224         // Check RTT observation count after throughput observation has been received. This ensures
225         // that executor has finished posting the RTT observation to the RTT listeners.
226         // NETWORK_QUALITY_OBSERVATION_SOURCE_URL_REQUEST
227         assertThat(rttListener.rttObservationCount(0)).isGreaterThan(0);
228 
229         // NETWORK_QUALITY_OBSERVATION_SOURCE_QUIC
230         assertThat(rttListener.rttObservationCount(2)).isGreaterThan(0);
231 
232         // Verify that effective connection type callback is received and
233         // effective connection type is correctly set.
234         assertThat(cronetEngine.getEffectiveConnectionType())
235                 .isNotEqualTo(EffectiveConnectionType.TYPE_UNKNOWN);
236 
237         // Verify that the HTTP RTT, transport RTT and downstream throughput
238         // estimates are available.
239         assertThat(cronetEngine.getHttpRttMs()).isAtLeast(0);
240         assertThat(cronetEngine.getTransportRttMs()).isAtLeast(0);
241         assertThat(cronetEngine.getDownstreamThroughputKbps()).isAtLeast(0);
242 
243         CronetTestUtil.nativeFlushWritePropertiesForTesting(cronetEngine);
244         assertThat(fileContainsString("local_prefs.json", "network_qualities")).isTrue();
245         cronetEngine.shutdown();
246     }
247 
248     @Test
249     @SmallTest
250     @SkipPresubmit(reason = "b/293141085 Tests that enable disk cache are flaky")
testMetricsWithQuic()251     public void testMetricsWithQuic() throws Exception {
252         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().getEngine();
253         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
254         cronetEngine.addRequestFinishedListener(requestFinishedListener);
255 
256         String quicURL = QuicTestServer.getServerURL() + "/simple.txt";
257         TestUrlRequestCallback callback = new TestUrlRequestCallback();
258 
259         UrlRequest.Builder requestBuilder =
260                 cronetEngine.newUrlRequestBuilder(quicURL, callback, callback.getExecutor());
261         Date startTime = new Date();
262         requestBuilder.build().start();
263         callback.blockForDone();
264         requestFinishedListener.blockUntilDone();
265         Date endTime = new Date();
266 
267         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
268         assertIsQuic(callback.getResponseInfoWithChecks());
269 
270         RequestFinishedInfo requestInfo = requestFinishedListener.getRequestInfo();
271         MetricsTestUtil.checkRequestFinishedInfo(requestInfo, quicURL, startTime, endTime);
272         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
273         MetricsTestUtil.checkHasConnectTiming(requestInfo.getMetrics(), startTime, endTime, true);
274 
275         // Second request should use the same connection and not have ConnectTiming numbers
276         callback = new TestUrlRequestCallback();
277         requestFinishedListener.reset();
278         requestBuilder =
279                 cronetEngine.newUrlRequestBuilder(quicURL, callback, callback.getExecutor());
280         startTime = new Date();
281         requestBuilder.build().start();
282         callback.blockForDone();
283         requestFinishedListener.blockUntilDone();
284         endTime = new Date();
285 
286         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
287         assertIsQuic(callback.getResponseInfoWithChecks());
288 
289         requestInfo = requestFinishedListener.getRequestInfo();
290         MetricsTestUtil.checkRequestFinishedInfo(requestInfo, quicURL, startTime, endTime);
291         assertThat(requestInfo.getFinishedReason()).isEqualTo(RequestFinishedInfo.SUCCEEDED);
292         MetricsTestUtil.checkNoConnectTiming(requestInfo.getMetrics());
293 
294         cronetEngine.shutdown();
295     }
296 
297     // Helper method to assert that the request is negotiated over QUIC.
assertIsQuic(UrlResponseInfo responseInfo)298     private void assertIsQuic(UrlResponseInfo responseInfo) {
299         String protocol = responseInfo.getNegotiatedProtocol();
300         assertWithMessage("Expected the negotiatedProtocol to be QUIC but was " + protocol)
301                 .that(protocol.startsWith("http/2+quic") || protocol.startsWith("h3"))
302                 .isTrue();
303     }
304 }
305