xref: /aosp_15_r20/external/curl/tests/http/test_02_download.py (revision 6236dae45794135f37c4eb022389c904c8b0090d)
1*6236dae4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6236dae4SAndroid Build Coastguard Worker# -*- coding: utf-8 -*-
3*6236dae4SAndroid Build Coastguard Worker#***************************************************************************
4*6236dae4SAndroid Build Coastguard Worker#                                  _   _ ____  _
5*6236dae4SAndroid Build Coastguard Worker#  Project                     ___| | | |  _ \| |
6*6236dae4SAndroid Build Coastguard Worker#                             / __| | | | |_) | |
7*6236dae4SAndroid Build Coastguard Worker#                            | (__| |_| |  _ <| |___
8*6236dae4SAndroid Build Coastguard Worker#                             \___|\___/|_| \_\_____|
9*6236dae4SAndroid Build Coastguard Worker#
10*6236dae4SAndroid Build Coastguard Worker# Copyright (C) Daniel Stenberg, <[email protected]>, et al.
11*6236dae4SAndroid Build Coastguard Worker#
12*6236dae4SAndroid Build Coastguard Worker# This software is licensed as described in the file COPYING, which
13*6236dae4SAndroid Build Coastguard Worker# you should have received as part of this distribution. The terms
14*6236dae4SAndroid Build Coastguard Worker# are also available at https://curl.se/docs/copyright.html.
15*6236dae4SAndroid Build Coastguard Worker#
16*6236dae4SAndroid Build Coastguard Worker# You may opt to use, copy, modify, merge, publish, distribute and/or sell
17*6236dae4SAndroid Build Coastguard Worker# copies of the Software, and permit persons to whom the Software is
18*6236dae4SAndroid Build Coastguard Worker# furnished to do so, under the terms of the COPYING file.
19*6236dae4SAndroid Build Coastguard Worker#
20*6236dae4SAndroid Build Coastguard Worker# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21*6236dae4SAndroid Build Coastguard Worker# KIND, either express or implied.
22*6236dae4SAndroid Build Coastguard Worker#
23*6236dae4SAndroid Build Coastguard Worker# SPDX-License-Identifier: curl
24*6236dae4SAndroid Build Coastguard Worker#
25*6236dae4SAndroid Build Coastguard Worker###########################################################################
26*6236dae4SAndroid Build Coastguard Worker#
27*6236dae4SAndroid Build Coastguard Workerimport difflib
28*6236dae4SAndroid Build Coastguard Workerimport filecmp
29*6236dae4SAndroid Build Coastguard Workerimport logging
30*6236dae4SAndroid Build Coastguard Workerimport math
31*6236dae4SAndroid Build Coastguard Workerimport os
32*6236dae4SAndroid Build Coastguard Workerimport re
33*6236dae4SAndroid Build Coastguard Workerfrom datetime import timedelta
34*6236dae4SAndroid Build Coastguard Workerimport pytest
35*6236dae4SAndroid Build Coastguard Worker
36*6236dae4SAndroid Build Coastguard Workerfrom testenv import Env, CurlClient, LocalClient
37*6236dae4SAndroid Build Coastguard Worker
38*6236dae4SAndroid Build Coastguard Worker
39*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__)
40*6236dae4SAndroid Build Coastguard Worker
41*6236dae4SAndroid Build Coastguard Worker
42*6236dae4SAndroid Build Coastguard Workerclass TestDownload:
43*6236dae4SAndroid Build Coastguard Worker
44*6236dae4SAndroid Build Coastguard Worker    @pytest.fixture(autouse=True, scope='class')
45*6236dae4SAndroid Build Coastguard Worker    def _class_scope(self, env, httpd, nghttpx):
46*6236dae4SAndroid Build Coastguard Worker        if env.have_h3():
47*6236dae4SAndroid Build Coastguard Worker            nghttpx.start_if_needed()
48*6236dae4SAndroid Build Coastguard Worker        httpd.clear_extra_configs()
49*6236dae4SAndroid Build Coastguard Worker        httpd.reload()
50*6236dae4SAndroid Build Coastguard Worker
51*6236dae4SAndroid Build Coastguard Worker    @pytest.fixture(autouse=True, scope='class')
52*6236dae4SAndroid Build Coastguard Worker    def _class_scope(self, env, httpd):
53*6236dae4SAndroid Build Coastguard Worker        indir = httpd.docs_dir
54*6236dae4SAndroid Build Coastguard Worker        env.make_data_file(indir=indir, fname="data-10k", fsize=10*1024)
55*6236dae4SAndroid Build Coastguard Worker        env.make_data_file(indir=indir, fname="data-100k", fsize=100*1024)
56*6236dae4SAndroid Build Coastguard Worker        env.make_data_file(indir=indir, fname="data-1m", fsize=1024*1024)
57*6236dae4SAndroid Build Coastguard Worker        env.make_data_file(indir=indir, fname="data-10m", fsize=10*1024*1024)
58*6236dae4SAndroid Build Coastguard Worker        env.make_data_file(indir=indir, fname="data-50m", fsize=50*1024*1024)
59*6236dae4SAndroid Build Coastguard Worker
60*6236dae4SAndroid Build Coastguard Worker    # download 1 file
61*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
62*6236dae4SAndroid Build Coastguard Worker    def test_02_01_download_1(self, env: Env, httpd, nghttpx, repeat, proto):
63*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
64*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
65*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
66*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
67*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[url], alpn_proto=proto)
68*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200)
69*6236dae4SAndroid Build Coastguard Worker
70*6236dae4SAndroid Build Coastguard Worker    # download 2 files
71*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
72*6236dae4SAndroid Build Coastguard Worker    def test_02_02_download_2(self, env: Env, httpd, nghttpx, repeat, proto):
73*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
74*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
75*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
76*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-1]'
77*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[url], alpn_proto=proto)
78*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=2)
79*6236dae4SAndroid Build Coastguard Worker
80*6236dae4SAndroid Build Coastguard Worker    # download 100 files sequentially
81*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
82*6236dae4SAndroid Build Coastguard Worker    def test_02_03_download_sequential(self, env: Env,
83*6236dae4SAndroid Build Coastguard Worker                                       httpd, nghttpx, repeat, proto):
84*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
85*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
86*6236dae4SAndroid Build Coastguard Worker        count = 10
87*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
88*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
89*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto)
90*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=count, connect_count=1)
91*6236dae4SAndroid Build Coastguard Worker
92*6236dae4SAndroid Build Coastguard Worker    # download 100 files parallel
93*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
94*6236dae4SAndroid Build Coastguard Worker    def test_02_04_download_parallel(self, env: Env,
95*6236dae4SAndroid Build Coastguard Worker                                     httpd, nghttpx, repeat, proto):
96*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
97*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
98*6236dae4SAndroid Build Coastguard Worker        count = 10
99*6236dae4SAndroid Build Coastguard Worker        max_parallel = 5
100*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
101*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
102*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
103*6236dae4SAndroid Build Coastguard Worker            '--parallel', '--parallel-max', f'{max_parallel}'
104*6236dae4SAndroid Build Coastguard Worker        ])
105*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=count)
106*6236dae4SAndroid Build Coastguard Worker        if proto == 'http/1.1':
107*6236dae4SAndroid Build Coastguard Worker            # http/1.1 parallel transfers will open multiple connections
108*6236dae4SAndroid Build Coastguard Worker            assert r.total_connects > 1, r.dump_logs()
109*6236dae4SAndroid Build Coastguard Worker        else:
110*6236dae4SAndroid Build Coastguard Worker            # http2 parallel transfers will use one connection (common limit is 100)
111*6236dae4SAndroid Build Coastguard Worker            assert r.total_connects == 1, r.dump_logs()
112*6236dae4SAndroid Build Coastguard Worker
113*6236dae4SAndroid Build Coastguard Worker    # download 500 files sequential
114*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
115*6236dae4SAndroid Build Coastguard Worker    def test_02_05_download_many_sequential(self, env: Env,
116*6236dae4SAndroid Build Coastguard Worker                                            httpd, nghttpx, repeat, proto):
117*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
118*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
119*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.curl_uses_lib('msh3'):
120*6236dae4SAndroid Build Coastguard Worker            pytest.skip("msh3 shaky here")
121*6236dae4SAndroid Build Coastguard Worker        count = 200
122*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
123*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
124*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto)
125*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=count)
126*6236dae4SAndroid Build Coastguard Worker        if proto == 'http/1.1':
127*6236dae4SAndroid Build Coastguard Worker            # http/1.1 parallel transfers will open multiple connections
128*6236dae4SAndroid Build Coastguard Worker            assert r.total_connects > 1, r.dump_logs()
129*6236dae4SAndroid Build Coastguard Worker        else:
130*6236dae4SAndroid Build Coastguard Worker            # http2 parallel transfers will use one connection (common limit is 100)
131*6236dae4SAndroid Build Coastguard Worker            assert r.total_connects == 1, r.dump_logs()
132*6236dae4SAndroid Build Coastguard Worker
133*6236dae4SAndroid Build Coastguard Worker    # download 500 files parallel
134*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2', 'h3'])
135*6236dae4SAndroid Build Coastguard Worker    def test_02_06_download_many_parallel(self, env: Env,
136*6236dae4SAndroid Build Coastguard Worker                                          httpd, nghttpx, repeat, proto):
137*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
138*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
139*6236dae4SAndroid Build Coastguard Worker        count = 200
140*6236dae4SAndroid Build Coastguard Worker        max_parallel = 50
141*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
142*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[000-{count-1}]'
143*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
144*6236dae4SAndroid Build Coastguard Worker            '--parallel', '--parallel-max', f'{max_parallel}'
145*6236dae4SAndroid Build Coastguard Worker        ])
146*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=count, connect_count=1)
147*6236dae4SAndroid Build Coastguard Worker
148*6236dae4SAndroid Build Coastguard Worker    # download files parallel, check connection reuse/multiplex
149*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2', 'h3'])
150*6236dae4SAndroid Build Coastguard Worker    def test_02_07_download_reuse(self, env: Env,
151*6236dae4SAndroid Build Coastguard Worker                                  httpd, nghttpx, repeat, proto):
152*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
153*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
154*6236dae4SAndroid Build Coastguard Worker        count = 200
155*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
156*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
157*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto,
158*6236dae4SAndroid Build Coastguard Worker                               with_stats=True, extra_args=[
159*6236dae4SAndroid Build Coastguard Worker            '--parallel', '--parallel-max', '200'
160*6236dae4SAndroid Build Coastguard Worker        ])
161*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=count)
162*6236dae4SAndroid Build Coastguard Worker        # should have used at most 2 connections only (test servers allow 100 req/conn)
163*6236dae4SAndroid Build Coastguard Worker        # it may be just 1 on slow systems where request are answered faster than
164*6236dae4SAndroid Build Coastguard Worker        # curl can exhaust the capacity or if curl runs with address-sanitizer speed
165*6236dae4SAndroid Build Coastguard Worker        assert r.total_connects <= 2, "h2 should use fewer connections here"
166*6236dae4SAndroid Build Coastguard Worker
167*6236dae4SAndroid Build Coastguard Worker    # download files parallel with http/1.1, check connection not reused
168*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1'])
169*6236dae4SAndroid Build Coastguard Worker    def test_02_07b_download_reuse(self, env: Env,
170*6236dae4SAndroid Build Coastguard Worker                                   httpd, nghttpx, repeat, proto):
171*6236dae4SAndroid Build Coastguard Worker        count = 6
172*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
173*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]'
174*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto,
175*6236dae4SAndroid Build Coastguard Worker                               with_stats=True, extra_args=[
176*6236dae4SAndroid Build Coastguard Worker            '--parallel'
177*6236dae4SAndroid Build Coastguard Worker        ])
178*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
179*6236dae4SAndroid Build Coastguard Worker        # http/1.1 should have used count connections
180*6236dae4SAndroid Build Coastguard Worker        assert r.total_connects == count, "http/1.1 should use this many connections"
181*6236dae4SAndroid Build Coastguard Worker
182*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
183*6236dae4SAndroid Build Coastguard Worker    def test_02_08_1MB_serial(self, env: Env,
184*6236dae4SAndroid Build Coastguard Worker                              httpd, nghttpx, repeat, proto):
185*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
186*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
187*6236dae4SAndroid Build Coastguard Worker        count = 5
188*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data-1m?[0-{count-1}]'
189*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
190*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto)
191*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
192*6236dae4SAndroid Build Coastguard Worker
193*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2', 'h3'])
194*6236dae4SAndroid Build Coastguard Worker    def test_02_09_1MB_parallel(self, env: Env,
195*6236dae4SAndroid Build Coastguard Worker                              httpd, nghttpx, repeat, proto):
196*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
197*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
198*6236dae4SAndroid Build Coastguard Worker        count = 5
199*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data-1m?[0-{count-1}]'
200*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
201*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
202*6236dae4SAndroid Build Coastguard Worker            '--parallel'
203*6236dae4SAndroid Build Coastguard Worker        ])
204*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
205*6236dae4SAndroid Build Coastguard Worker
206*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests")
207*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs")
208*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
209*6236dae4SAndroid Build Coastguard Worker    def test_02_10_10MB_serial(self, env: Env,
210*6236dae4SAndroid Build Coastguard Worker                              httpd, nghttpx, repeat, proto):
211*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
212*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
213*6236dae4SAndroid Build Coastguard Worker        count = 3
214*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data-10m?[0-{count-1}]'
215*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
216*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto)
217*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
218*6236dae4SAndroid Build Coastguard Worker
219*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests")
220*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs")
221*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2', 'h3'])
222*6236dae4SAndroid Build Coastguard Worker    def test_02_11_10MB_parallel(self, env: Env,
223*6236dae4SAndroid Build Coastguard Worker                              httpd, nghttpx, repeat, proto):
224*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
225*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
226*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.curl_uses_lib('msh3'):
227*6236dae4SAndroid Build Coastguard Worker            pytest.skip("msh3 stalls here")
228*6236dae4SAndroid Build Coastguard Worker        count = 3
229*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data-10m?[0-{count-1}]'
230*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
231*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
232*6236dae4SAndroid Build Coastguard Worker            '--parallel'
233*6236dae4SAndroid Build Coastguard Worker        ])
234*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
235*6236dae4SAndroid Build Coastguard Worker
236*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2', 'h3'])
237*6236dae4SAndroid Build Coastguard Worker    def test_02_12_head_serial_https(self, env: Env,
238*6236dae4SAndroid Build Coastguard Worker                                     httpd, nghttpx, repeat, proto):
239*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
240*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
241*6236dae4SAndroid Build Coastguard Worker        count = 5
242*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/data-10m?[0-{count-1}]'
243*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
244*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
245*6236dae4SAndroid Build Coastguard Worker            '--head'
246*6236dae4SAndroid Build Coastguard Worker        ])
247*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
248*6236dae4SAndroid Build Coastguard Worker
249*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2'])
250*6236dae4SAndroid Build Coastguard Worker    def test_02_13_head_serial_h2c(self, env: Env,
251*6236dae4SAndroid Build Coastguard Worker                                    httpd, nghttpx, repeat, proto):
252*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
253*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
254*6236dae4SAndroid Build Coastguard Worker        count = 5
255*6236dae4SAndroid Build Coastguard Worker        urln = f'http://{env.domain1}:{env.http_port}/data-10m?[0-{count-1}]'
256*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
257*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
258*6236dae4SAndroid Build Coastguard Worker            '--head', '--http2-prior-knowledge', '--fail-early'
259*6236dae4SAndroid Build Coastguard Worker        ])
260*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
261*6236dae4SAndroid Build Coastguard Worker
262*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2', 'h3'])
263*6236dae4SAndroid Build Coastguard Worker    def test_02_14_not_found(self, env: Env, httpd, nghttpx, repeat, proto):
264*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
265*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
266*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.curl_uses_lib('msh3'):
267*6236dae4SAndroid Build Coastguard Worker            pytest.skip("msh3 stalls here")
268*6236dae4SAndroid Build Coastguard Worker        count = 5
269*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/not-found?[0-{count-1}]'
270*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
271*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
272*6236dae4SAndroid Build Coastguard Worker            '--parallel'
273*6236dae4SAndroid Build Coastguard Worker        ])
274*6236dae4SAndroid Build Coastguard Worker        r.check_stats(count=count, http_status=404, exitcode=0,
275*6236dae4SAndroid Build Coastguard Worker                      remote_port=env.port_for(alpn_proto=proto),
276*6236dae4SAndroid Build Coastguard Worker                      remote_ip='127.0.0.1')
277*6236dae4SAndroid Build Coastguard Worker
278*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['h2', 'h3'])
279*6236dae4SAndroid Build Coastguard Worker    def test_02_15_fail_not_found(self, env: Env, httpd, nghttpx, repeat, proto):
280*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
281*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
282*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.curl_uses_lib('msh3'):
283*6236dae4SAndroid Build Coastguard Worker            pytest.skip("msh3 stalls here")
284*6236dae4SAndroid Build Coastguard Worker        count = 5
285*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, proto)}/not-found?[0-{count-1}]'
286*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
287*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
288*6236dae4SAndroid Build Coastguard Worker            '--fail'
289*6236dae4SAndroid Build Coastguard Worker        ])
290*6236dae4SAndroid Build Coastguard Worker        r.check_stats(count=count, http_status=404, exitcode=22,
291*6236dae4SAndroid Build Coastguard Worker                      remote_port=env.port_for(alpn_proto=proto),
292*6236dae4SAndroid Build Coastguard Worker                      remote_ip='127.0.0.1')
293*6236dae4SAndroid Build Coastguard Worker
294*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests")
295*6236dae4SAndroid Build Coastguard Worker    def test_02_20_h2_small_frames(self, env: Env, httpd, repeat):
296*6236dae4SAndroid Build Coastguard Worker        # Test case to reproduce content corruption as observed in
297*6236dae4SAndroid Build Coastguard Worker        # https://github.com/curl/curl/issues/10525
298*6236dae4SAndroid Build Coastguard Worker        # To reliably reproduce, we need an Apache httpd that supports
299*6236dae4SAndroid Build Coastguard Worker        # setting smaller frame sizes. This is not released yet, we
300*6236dae4SAndroid Build Coastguard Worker        # test if it works and back out if not.
301*6236dae4SAndroid Build Coastguard Worker        httpd.set_extra_config(env.domain1, lines=[
302*6236dae4SAndroid Build Coastguard Worker            'H2MaxDataFrameLen 1024',
303*6236dae4SAndroid Build Coastguard Worker        ])
304*6236dae4SAndroid Build Coastguard Worker        assert httpd.stop()
305*6236dae4SAndroid Build Coastguard Worker        if not httpd.start():
306*6236dae4SAndroid Build Coastguard Worker            # no, not supported, bail out
307*6236dae4SAndroid Build Coastguard Worker            httpd.set_extra_config(env.domain1, lines=None)
308*6236dae4SAndroid Build Coastguard Worker            assert httpd.start()
309*6236dae4SAndroid Build Coastguard Worker            pytest.skip('H2MaxDataFrameLen not supported')
310*6236dae4SAndroid Build Coastguard Worker        # ok, make 100 downloads with 2 parallel running and they
311*6236dae4SAndroid Build Coastguard Worker        # are expected to stumble into the issue when using `lib/http2.c`
312*6236dae4SAndroid Build Coastguard Worker        # from curl 7.88.0
313*6236dae4SAndroid Build Coastguard Worker        count = 5
314*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1, "h2")}/data-1m?[0-{count-1}]'
315*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
316*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto="h2", extra_args=[
317*6236dae4SAndroid Build Coastguard Worker            '--parallel', '--parallel-max', '2'
318*6236dae4SAndroid Build Coastguard Worker        ])
319*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
320*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, 'data-1m')
321*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(curl, srcfile, count)
322*6236dae4SAndroid Build Coastguard Worker        # restore httpd defaults
323*6236dae4SAndroid Build Coastguard Worker        httpd.set_extra_config(env.domain1, lines=None)
324*6236dae4SAndroid Build Coastguard Worker        assert httpd.stop()
325*6236dae4SAndroid Build Coastguard Worker        assert httpd.start()
326*6236dae4SAndroid Build Coastguard Worker
327*6236dae4SAndroid Build Coastguard Worker    # download via lib client, 1 at a time, pause/resume at different offsets
328*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("pause_offset", [0, 10*1024, 100*1023, 640000])
329*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
330*6236dae4SAndroid Build Coastguard Worker    def test_02_21_lib_serial(self, env: Env, httpd, nghttpx, proto, pause_offset, repeat):
331*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
332*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
333*6236dae4SAndroid Build Coastguard Worker        count = 2
334*6236dae4SAndroid Build Coastguard Worker        docname = 'data-10m'
335*6236dae4SAndroid Build Coastguard Worker        url = f'https://localhost:{env.https_port}/{docname}'
336*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='hx-download', env=env)
337*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
338*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
339*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[
340*6236dae4SAndroid Build Coastguard Worker             '-n', f'{count}', '-P', f'{pause_offset}', '-V', proto, url
341*6236dae4SAndroid Build Coastguard Worker        ])
342*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
343*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, docname)
344*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(client, srcfile, count)
345*6236dae4SAndroid Build Coastguard Worker
346*6236dae4SAndroid Build Coastguard Worker    # download via lib client, several at a time, pause/resume
347*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("pause_offset", [100*1023])
348*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
349*6236dae4SAndroid Build Coastguard Worker    def test_02_22_lib_parallel_resume(self, env: Env, httpd, nghttpx, proto, pause_offset, repeat):
350*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
351*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
352*6236dae4SAndroid Build Coastguard Worker        count = 2
353*6236dae4SAndroid Build Coastguard Worker        max_parallel = 5
354*6236dae4SAndroid Build Coastguard Worker        docname = 'data-10m'
355*6236dae4SAndroid Build Coastguard Worker        url = f'https://localhost:{env.https_port}/{docname}'
356*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='hx-download', env=env)
357*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
358*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
359*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[
360*6236dae4SAndroid Build Coastguard Worker            '-n', f'{count}', '-m', f'{max_parallel}',
361*6236dae4SAndroid Build Coastguard Worker            '-P', f'{pause_offset}', '-V', proto, url
362*6236dae4SAndroid Build Coastguard Worker        ])
363*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
364*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, docname)
365*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(client, srcfile, count)
366*6236dae4SAndroid Build Coastguard Worker
367*6236dae4SAndroid Build Coastguard Worker    # download, several at a time, pause and abort paused
368*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
369*6236dae4SAndroid Build Coastguard Worker    def test_02_23a_lib_abort_paused(self, env: Env, httpd, nghttpx, proto, repeat):
370*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
371*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
372*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.curl_uses_ossl_quic():
373*6236dae4SAndroid Build Coastguard Worker            pytest.skip('OpenSSL QUIC fails here')
374*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.ci_run and env.curl_uses_lib('quiche'):
375*6236dae4SAndroid Build Coastguard Worker            pytest.skip("fails in CI, but works locally for unknown reasons")
376*6236dae4SAndroid Build Coastguard Worker        count = 10
377*6236dae4SAndroid Build Coastguard Worker        max_parallel = 5
378*6236dae4SAndroid Build Coastguard Worker        if proto in ['h2', 'h3']:
379*6236dae4SAndroid Build Coastguard Worker            pause_offset = 64 * 1024
380*6236dae4SAndroid Build Coastguard Worker        else:
381*6236dae4SAndroid Build Coastguard Worker            pause_offset = 12 * 1024
382*6236dae4SAndroid Build Coastguard Worker        docname = 'data-1m'
383*6236dae4SAndroid Build Coastguard Worker        url = f'https://localhost:{env.https_port}/{docname}'
384*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='hx-download', env=env)
385*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
386*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
387*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[
388*6236dae4SAndroid Build Coastguard Worker            '-n', f'{count}', '-m', f'{max_parallel}', '-a',
389*6236dae4SAndroid Build Coastguard Worker            '-P', f'{pause_offset}', '-V', proto, url
390*6236dae4SAndroid Build Coastguard Worker        ])
391*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
392*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, docname)
393*6236dae4SAndroid Build Coastguard Worker        # downloads should be there, but not necessarily complete
394*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(client, srcfile, count, complete=False)
395*6236dae4SAndroid Build Coastguard Worker
396*6236dae4SAndroid Build Coastguard Worker    # download, several at a time, abort after n bytes
397*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
398*6236dae4SAndroid Build Coastguard Worker    def test_02_23b_lib_abort_offset(self, env: Env, httpd, nghttpx, proto, repeat):
399*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
400*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
401*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.curl_uses_ossl_quic():
402*6236dae4SAndroid Build Coastguard Worker            pytest.skip('OpenSSL QUIC fails here')
403*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.ci_run and env.curl_uses_lib('quiche'):
404*6236dae4SAndroid Build Coastguard Worker            pytest.skip("fails in CI, but works locally for unknown reasons")
405*6236dae4SAndroid Build Coastguard Worker        count = 10
406*6236dae4SAndroid Build Coastguard Worker        max_parallel = 5
407*6236dae4SAndroid Build Coastguard Worker        if proto in ['h2', 'h3']:
408*6236dae4SAndroid Build Coastguard Worker            abort_offset = 64 * 1024
409*6236dae4SAndroid Build Coastguard Worker        else:
410*6236dae4SAndroid Build Coastguard Worker            abort_offset = 12 * 1024
411*6236dae4SAndroid Build Coastguard Worker        docname = 'data-1m'
412*6236dae4SAndroid Build Coastguard Worker        url = f'https://localhost:{env.https_port}/{docname}'
413*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='hx-download', env=env)
414*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
415*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
416*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[
417*6236dae4SAndroid Build Coastguard Worker            '-n', f'{count}', '-m', f'{max_parallel}', '-a',
418*6236dae4SAndroid Build Coastguard Worker            '-A', f'{abort_offset}', '-V', proto, url
419*6236dae4SAndroid Build Coastguard Worker        ])
420*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
421*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, docname)
422*6236dae4SAndroid Build Coastguard Worker        # downloads should be there, but not necessarily complete
423*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(client, srcfile, count, complete=False)
424*6236dae4SAndroid Build Coastguard Worker
425*6236dae4SAndroid Build Coastguard Worker    # download, several at a time, abort after n bytes
426*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
427*6236dae4SAndroid Build Coastguard Worker    def test_02_23c_lib_fail_offset(self, env: Env, httpd, nghttpx, proto, repeat):
428*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
429*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
430*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.curl_uses_ossl_quic():
431*6236dae4SAndroid Build Coastguard Worker            pytest.skip('OpenSSL QUIC fails here')
432*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and env.ci_run and env.curl_uses_lib('quiche'):
433*6236dae4SAndroid Build Coastguard Worker            pytest.skip("fails in CI, but works locally for unknown reasons")
434*6236dae4SAndroid Build Coastguard Worker        count = 10
435*6236dae4SAndroid Build Coastguard Worker        max_parallel = 5
436*6236dae4SAndroid Build Coastguard Worker        if proto in ['h2', 'h3']:
437*6236dae4SAndroid Build Coastguard Worker            fail_offset = 64 * 1024
438*6236dae4SAndroid Build Coastguard Worker        else:
439*6236dae4SAndroid Build Coastguard Worker            fail_offset = 12 * 1024
440*6236dae4SAndroid Build Coastguard Worker        docname = 'data-1m'
441*6236dae4SAndroid Build Coastguard Worker        url = f'https://localhost:{env.https_port}/{docname}'
442*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='hx-download', env=env)
443*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
444*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
445*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[
446*6236dae4SAndroid Build Coastguard Worker            '-n', f'{count}', '-m', f'{max_parallel}', '-a',
447*6236dae4SAndroid Build Coastguard Worker            '-F', f'{fail_offset}', '-V', proto, url
448*6236dae4SAndroid Build Coastguard Worker        ])
449*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
450*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, docname)
451*6236dae4SAndroid Build Coastguard Worker        # downloads should be there, but not necessarily complete
452*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(client, srcfile, count, complete=False)
453*6236dae4SAndroid Build Coastguard Worker
454*6236dae4SAndroid Build Coastguard Worker    # speed limited download
455*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
456*6236dae4SAndroid Build Coastguard Worker    def test_02_24_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat):
457*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
458*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
459*6236dae4SAndroid Build Coastguard Worker        count = 1
460*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.authority_for(env.domain1, proto)}/data-1m'
461*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
462*6236dae4SAndroid Build Coastguard Worker        speed_limit = 384 * 1024
463*6236dae4SAndroid Build Coastguard Worker        min_duration = math.floor((1024 * 1024)/speed_limit)
464*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[url], alpn_proto=proto, extra_args=[
465*6236dae4SAndroid Build Coastguard Worker            '--limit-rate', f'{speed_limit}'
466*6236dae4SAndroid Build Coastguard Worker        ])
467*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
468*6236dae4SAndroid Build Coastguard Worker        assert r.duration > timedelta(seconds=min_duration), \
469*6236dae4SAndroid Build Coastguard Worker            f'rate limited transfer should take more than {min_duration}s, '\
470*6236dae4SAndroid Build Coastguard Worker            f'not {r.duration}'
471*6236dae4SAndroid Build Coastguard Worker
472*6236dae4SAndroid Build Coastguard Worker    # make extreme parallel h2 upgrades, check invalid conn reuse
473*6236dae4SAndroid Build Coastguard Worker    # before protocol switch has happened
474*6236dae4SAndroid Build Coastguard Worker    def test_02_25_h2_upgrade_x(self, env: Env, httpd, repeat):
475*6236dae4SAndroid Build Coastguard Worker        url = f'http://localhost:{env.http_port}/data-100k'
476*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='h2-upgrade-extreme', env=env, timeout=15)
477*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
478*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
479*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[url])
480*6236dae4SAndroid Build Coastguard Worker        assert r.exit_code == 0, f'{client.dump_logs()}'
481*6236dae4SAndroid Build Coastguard Worker
482*6236dae4SAndroid Build Coastguard Worker    # Special client that tests TLS session reuse in parallel transfers
483*6236dae4SAndroid Build Coastguard Worker    # TODO: just uses a single connection for h2/h3. Not sure how to prevent that
484*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
485*6236dae4SAndroid Build Coastguard Worker    def test_02_26_session_shared_reuse(self, env: Env, proto, httpd, nghttpx, repeat):
486*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.authority_for(env.domain1, proto)}/data-100k'
487*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='tls-session-reuse', env=env)
488*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
489*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
490*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[proto, url])
491*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
492*6236dae4SAndroid Build Coastguard Worker
493*6236dae4SAndroid Build Coastguard Worker    # test on paused transfers, based on issue #11982
494*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
495*6236dae4SAndroid Build Coastguard Worker    def test_02_27a_paused_no_cl(self, env: Env, httpd, nghttpx, proto, repeat):
496*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.authority_for(env.domain1, proto)}' \
497*6236dae4SAndroid Build Coastguard Worker            '/curltest/tweak/?&chunks=6&chunk_size=8000'
498*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(env=env, name='h2-pausing')
499*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=['-V', proto, url])
500*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
501*6236dae4SAndroid Build Coastguard Worker
502*6236dae4SAndroid Build Coastguard Worker    # test on paused transfers, based on issue #11982
503*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
504*6236dae4SAndroid Build Coastguard Worker    def test_02_27b_paused_no_cl(self, env: Env, httpd, nghttpx, proto, repeat):
505*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.authority_for(env.domain1, proto)}' \
506*6236dae4SAndroid Build Coastguard Worker            '/curltest/tweak/?error=502'
507*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(env=env, name='h2-pausing')
508*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=['-V', proto, url])
509*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
510*6236dae4SAndroid Build Coastguard Worker
511*6236dae4SAndroid Build Coastguard Worker    # test on paused transfers, based on issue #11982
512*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
513*6236dae4SAndroid Build Coastguard Worker    def test_02_27c_paused_no_cl(self, env: Env, httpd, nghttpx, proto, repeat):
514*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.authority_for(env.domain1, proto)}' \
515*6236dae4SAndroid Build Coastguard Worker            '/curltest/tweak/?status=200&chunks=1&chunk_size=100'
516*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(env=env, name='h2-pausing')
517*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=['-V', proto, url])
518*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
519*6236dae4SAndroid Build Coastguard Worker
520*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
521*6236dae4SAndroid Build Coastguard Worker    def test_02_28_get_compressed(self, env: Env, httpd, nghttpx, repeat, proto):
522*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
523*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
524*6236dae4SAndroid Build Coastguard Worker        count = 1
525*6236dae4SAndroid Build Coastguard Worker        urln = f'https://{env.authority_for(env.domain1brotli, proto)}/data-100k?[0-{count-1}]'
526*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
527*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
528*6236dae4SAndroid Build Coastguard Worker            '--compressed'
529*6236dae4SAndroid Build Coastguard Worker        ])
530*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(code=0)
531*6236dae4SAndroid Build Coastguard Worker        r.check_response(count=count, http_status=200)
532*6236dae4SAndroid Build Coastguard Worker
533*6236dae4SAndroid Build Coastguard Worker    def check_downloads(self, client, srcfile: str, count: int,
534*6236dae4SAndroid Build Coastguard Worker                        complete: bool = True):
535*6236dae4SAndroid Build Coastguard Worker        for i in range(count):
536*6236dae4SAndroid Build Coastguard Worker            dfile = client.download_file(i)
537*6236dae4SAndroid Build Coastguard Worker            assert os.path.exists(dfile)
538*6236dae4SAndroid Build Coastguard Worker            if complete and not filecmp.cmp(srcfile, dfile, shallow=False):
539*6236dae4SAndroid Build Coastguard Worker                diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
540*6236dae4SAndroid Build Coastguard Worker                                                    b=open(dfile).readlines(),
541*6236dae4SAndroid Build Coastguard Worker                                                    fromfile=srcfile,
542*6236dae4SAndroid Build Coastguard Worker                                                    tofile=dfile,
543*6236dae4SAndroid Build Coastguard Worker                                                    n=1))
544*6236dae4SAndroid Build Coastguard Worker                assert False, f'download {dfile} differs:\n{diff}'
545*6236dae4SAndroid Build Coastguard Worker
546*6236dae4SAndroid Build Coastguard Worker    # download via lib client, 1 at a time, pause/resume at different offsets
547*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("pause_offset", [0, 10*1024, 100*1023, 640000])
548*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
549*6236dae4SAndroid Build Coastguard Worker    def test_02_29_h2_lib_serial(self, env: Env, httpd, nghttpx, proto, pause_offset, repeat):
550*6236dae4SAndroid Build Coastguard Worker        count = 2
551*6236dae4SAndroid Build Coastguard Worker        docname = 'data-10m'
552*6236dae4SAndroid Build Coastguard Worker        url = f'https://localhost:{env.https_port}/{docname}'
553*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='hx-download', env=env)
554*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
555*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
556*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[
557*6236dae4SAndroid Build Coastguard Worker             '-n', f'{count}', '-P', f'{pause_offset}', '-V', proto, url
558*6236dae4SAndroid Build Coastguard Worker        ])
559*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
560*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, docname)
561*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(client, srcfile, count)
562*6236dae4SAndroid Build Coastguard Worker
563*6236dae4SAndroid Build Coastguard Worker    # download parallel with prior knowledge
564*6236dae4SAndroid Build Coastguard Worker    def test_02_30_parallel_prior_knowledge(self, env: Env, httpd):
565*6236dae4SAndroid Build Coastguard Worker        count = 3
566*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
567*6236dae4SAndroid Build Coastguard Worker        urln = f'http://{env.domain1}:{env.http_port}/data.json?[0-{count-1}]'
568*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], extra_args=[
569*6236dae4SAndroid Build Coastguard Worker            '--parallel', '--http2-prior-knowledge'
570*6236dae4SAndroid Build Coastguard Worker        ])
571*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=count)
572*6236dae4SAndroid Build Coastguard Worker        assert r.total_connects == 1, r.dump_logs()
573*6236dae4SAndroid Build Coastguard Worker
574*6236dae4SAndroid Build Coastguard Worker    # download parallel with h2 "Upgrade:"
575*6236dae4SAndroid Build Coastguard Worker    def test_02_31_parallel_upgrade(self, env: Env, httpd, nghttpx):
576*6236dae4SAndroid Build Coastguard Worker        count = 3
577*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=env)
578*6236dae4SAndroid Build Coastguard Worker        urln = f'http://{env.domain1}:{env.http_port}/data.json?[0-{count-1}]'
579*6236dae4SAndroid Build Coastguard Worker        r = curl.http_download(urls=[urln], extra_args=[
580*6236dae4SAndroid Build Coastguard Worker            '--parallel', '--http2'
581*6236dae4SAndroid Build Coastguard Worker        ])
582*6236dae4SAndroid Build Coastguard Worker        r.check_response(http_status=200, count=count)
583*6236dae4SAndroid Build Coastguard Worker        # we see 3 connections, because Apache only every serves a single
584*6236dae4SAndroid Build Coastguard Worker        # request via Upgrade: and then closed the connection.
585*6236dae4SAndroid Build Coastguard Worker        assert r.total_connects == 3, r.dump_logs()
586*6236dae4SAndroid Build Coastguard Worker
587*6236dae4SAndroid Build Coastguard Worker    # nghttpx is the only server we have that supports TLS early data
588*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx")
589*6236dae4SAndroid Build Coastguard Worker    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
590*6236dae4SAndroid Build Coastguard Worker    def test_02_32_earlydata(self, env: Env, httpd, nghttpx, proto):
591*6236dae4SAndroid Build Coastguard Worker        if not env.curl_uses_lib('gnutls'):
592*6236dae4SAndroid Build Coastguard Worker            pytest.skip('TLS earlydata only implemented in GnuTLS')
593*6236dae4SAndroid Build Coastguard Worker        if proto == 'h3' and not env.have_h3():
594*6236dae4SAndroid Build Coastguard Worker            pytest.skip("h3 not supported")
595*6236dae4SAndroid Build Coastguard Worker        count = 2
596*6236dae4SAndroid Build Coastguard Worker        docname = 'data-10k'
597*6236dae4SAndroid Build Coastguard Worker        # we want this test to always connect to nghttpx, since it is
598*6236dae4SAndroid Build Coastguard Worker        # the only server we have that supports TLS earlydata
599*6236dae4SAndroid Build Coastguard Worker        port = env.port_for(proto)
600*6236dae4SAndroid Build Coastguard Worker        if proto != 'h3':
601*6236dae4SAndroid Build Coastguard Worker            port = env.nghttpx_https_port
602*6236dae4SAndroid Build Coastguard Worker        url = f'https://{env.domain1}:{port}/{docname}'
603*6236dae4SAndroid Build Coastguard Worker        client = LocalClient(name='hx-download', env=env)
604*6236dae4SAndroid Build Coastguard Worker        if not client.exists():
605*6236dae4SAndroid Build Coastguard Worker            pytest.skip(f'example client not built: {client.name}')
606*6236dae4SAndroid Build Coastguard Worker        r = client.run(args=[
607*6236dae4SAndroid Build Coastguard Worker             '-n', f'{count}',
608*6236dae4SAndroid Build Coastguard Worker             '-e',  # use TLS earlydata
609*6236dae4SAndroid Build Coastguard Worker             '-f',  # forbid reuse of connections
610*6236dae4SAndroid Build Coastguard Worker             '-r', f'{env.domain1}:{port}:127.0.0.1',
611*6236dae4SAndroid Build Coastguard Worker             '-V', proto, url
612*6236dae4SAndroid Build Coastguard Worker        ])
613*6236dae4SAndroid Build Coastguard Worker        r.check_exit_code(0)
614*6236dae4SAndroid Build Coastguard Worker        srcfile = os.path.join(httpd.docs_dir, docname)
615*6236dae4SAndroid Build Coastguard Worker        self.check_downloads(client, srcfile, count)
616*6236dae4SAndroid Build Coastguard Worker        # check that TLS earlydata worked as expected
617*6236dae4SAndroid Build Coastguard Worker        earlydata = {}
618*6236dae4SAndroid Build Coastguard Worker        reused_session = False
619*6236dae4SAndroid Build Coastguard Worker        for line in r.trace_lines:
620*6236dae4SAndroid Build Coastguard Worker            m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
621*6236dae4SAndroid Build Coastguard Worker            if m:
622*6236dae4SAndroid Build Coastguard Worker                earlydata[int(m.group(1))] = int(m.group(2))
623*6236dae4SAndroid Build Coastguard Worker                continue
624*6236dae4SAndroid Build Coastguard Worker            m = re.match(r'\[1-1] \* SSL reusing session.*', line)
625*6236dae4SAndroid Build Coastguard Worker            if m:
626*6236dae4SAndroid Build Coastguard Worker                reused_session = True
627*6236dae4SAndroid Build Coastguard Worker        assert reused_session, 'session was not reused for 2nd transfer'
628*6236dae4SAndroid Build Coastguard Worker        assert earlydata[0] == 0, f'{earlydata}'
629*6236dae4SAndroid Build Coastguard Worker        if proto == 'http/1.1':
630*6236dae4SAndroid Build Coastguard Worker            assert earlydata[1] == 69, f'{earlydata}'
631*6236dae4SAndroid Build Coastguard Worker        elif proto == 'h2':
632*6236dae4SAndroid Build Coastguard Worker            assert earlydata[1] == 107, f'{earlydata}'
633*6236dae4SAndroid Build Coastguard Worker        elif proto == 'h3':
634*6236dae4SAndroid Build Coastguard Worker            # not implemented
635*6236dae4SAndroid Build Coastguard Worker            assert earlydata[1] == 0, f'{earlydata}'
636