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