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