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 os 31*6236dae4SAndroid Build Coastguard Workerimport re 32*6236dae4SAndroid Build Coastguard Workerimport pytest 33*6236dae4SAndroid Build Coastguard Worker 34*6236dae4SAndroid Build Coastguard Workerfrom testenv import Env, CurlClient, Caddy, LocalClient 35*6236dae4SAndroid Build Coastguard Worker 36*6236dae4SAndroid Build Coastguard Worker 37*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__) 38*6236dae4SAndroid Build Coastguard Worker 39*6236dae4SAndroid Build Coastguard Worker 40*6236dae4SAndroid Build Coastguard Worker@pytest.mark.skipif(condition=not Env.has_caddy(), reason="missing caddy") 41*6236dae4SAndroid Build Coastguard Worker@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL") 42*6236dae4SAndroid Build Coastguard Workerclass TestCaddy: 43*6236dae4SAndroid Build Coastguard Worker 44*6236dae4SAndroid Build Coastguard Worker @pytest.fixture(autouse=True, scope='class') 45*6236dae4SAndroid Build Coastguard Worker def caddy(self, env): 46*6236dae4SAndroid Build Coastguard Worker caddy = Caddy(env=env) 47*6236dae4SAndroid Build Coastguard Worker assert caddy.start() 48*6236dae4SAndroid Build Coastguard Worker yield caddy 49*6236dae4SAndroid Build Coastguard Worker caddy.stop() 50*6236dae4SAndroid Build Coastguard Worker 51*6236dae4SAndroid Build Coastguard Worker def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): 52*6236dae4SAndroid Build Coastguard Worker fpath = os.path.join(docs_dir, fname) 53*6236dae4SAndroid Build Coastguard Worker data1k = 1024*'x' 54*6236dae4SAndroid Build Coastguard Worker flen = 0 55*6236dae4SAndroid Build Coastguard Worker with open(fpath, 'w') as fd: 56*6236dae4SAndroid Build Coastguard Worker while flen < fsize: 57*6236dae4SAndroid Build Coastguard Worker fd.write(data1k) 58*6236dae4SAndroid Build Coastguard Worker flen += len(data1k) 59*6236dae4SAndroid Build Coastguard Worker return flen 60*6236dae4SAndroid Build Coastguard Worker 61*6236dae4SAndroid Build Coastguard Worker @pytest.fixture(autouse=True, scope='class') 62*6236dae4SAndroid Build Coastguard Worker def _class_scope(self, env, caddy): 63*6236dae4SAndroid Build Coastguard Worker self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10*1024) 64*6236dae4SAndroid Build Coastguard Worker self._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024*1024) 65*6236dae4SAndroid Build Coastguard Worker self._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5*1024*1024) 66*6236dae4SAndroid Build Coastguard Worker self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10*1024*1024) 67*6236dae4SAndroid Build Coastguard Worker self._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100*1024*1024) 68*6236dae4SAndroid Build Coastguard Worker env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) 69*6236dae4SAndroid Build Coastguard Worker 70*6236dae4SAndroid Build Coastguard Worker # download 1 file 71*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 72*6236dae4SAndroid Build Coastguard Worker def test_08_01_download_1(self, env: Env, caddy: Caddy, repeat, proto): 73*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3_curl(): 74*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported in curl") 75*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 76*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 itself crashes") 77*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 78*6236dae4SAndroid Build Coastguard Worker url = f'https://{env.domain1}:{caddy.port}/data.json' 79*6236dae4SAndroid Build Coastguard Worker r = curl.http_download(urls=[url], alpn_proto=proto) 80*6236dae4SAndroid Build Coastguard Worker r.check_response(count=1, http_status=200) 81*6236dae4SAndroid Build Coastguard Worker 82*6236dae4SAndroid Build Coastguard Worker # download 1MB files sequentially 83*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 84*6236dae4SAndroid Build Coastguard Worker def test_08_02_download_1mb_sequential(self, env: Env, caddy: Caddy, 85*6236dae4SAndroid Build Coastguard Worker repeat, proto): 86*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3_curl(): 87*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported in curl") 88*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 89*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 itself crashes") 90*6236dae4SAndroid Build Coastguard Worker count = 50 91*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 92*6236dae4SAndroid Build Coastguard Worker urln = f'https://{env.domain1}:{caddy.port}/data1.data?[0-{count-1}]' 93*6236dae4SAndroid Build Coastguard Worker r = curl.http_download(urls=[urln], alpn_proto=proto) 94*6236dae4SAndroid Build Coastguard Worker r.check_response(count=count, http_status=200, connect_count=1) 95*6236dae4SAndroid Build Coastguard Worker 96*6236dae4SAndroid Build Coastguard Worker # download 1MB files parallel 97*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 98*6236dae4SAndroid Build Coastguard Worker def test_08_03_download_1mb_parallel(self, env: Env, caddy: Caddy, 99*6236dae4SAndroid Build Coastguard Worker repeat, proto): 100*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3_curl(): 101*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported in curl") 102*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 103*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 itself crashes") 104*6236dae4SAndroid Build Coastguard Worker count = 20 105*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 106*6236dae4SAndroid Build Coastguard Worker urln = f'https://{env.domain1}:{caddy.port}/data1.data?[0-{count-1}]' 107*6236dae4SAndroid Build Coastguard Worker r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[ 108*6236dae4SAndroid Build Coastguard Worker '--parallel' 109*6236dae4SAndroid Build Coastguard Worker ]) 110*6236dae4SAndroid Build Coastguard Worker r.check_response(count=count, http_status=200) 111*6236dae4SAndroid Build Coastguard Worker if proto == 'http/1.1': 112*6236dae4SAndroid Build Coastguard Worker # http/1.1 parallel transfers will open multiple connections 113*6236dae4SAndroid Build Coastguard Worker assert r.total_connects > 1, r.dump_logs() 114*6236dae4SAndroid Build Coastguard Worker else: 115*6236dae4SAndroid Build Coastguard Worker assert r.total_connects == 1, r.dump_logs() 116*6236dae4SAndroid Build Coastguard Worker 117*6236dae4SAndroid Build Coastguard Worker # download 5MB files sequentially 118*6236dae4SAndroid Build Coastguard Worker @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests") 119*6236dae4SAndroid Build Coastguard Worker @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs") 120*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['h2', 'h3']) 121*6236dae4SAndroid Build Coastguard Worker def test_08_04a_download_10mb_sequential(self, env: Env, caddy: Caddy, 122*6236dae4SAndroid Build Coastguard Worker repeat, proto): 123*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3_curl(): 124*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported in curl") 125*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 126*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 itself crashes") 127*6236dae4SAndroid Build Coastguard Worker count = 40 128*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 129*6236dae4SAndroid Build Coastguard Worker urln = f'https://{env.domain1}:{caddy.port}/data5.data?[0-{count-1}]' 130*6236dae4SAndroid Build Coastguard Worker r = curl.http_download(urls=[urln], alpn_proto=proto) 131*6236dae4SAndroid Build Coastguard Worker r.check_response(count=count, http_status=200, connect_count=1) 132*6236dae4SAndroid Build Coastguard Worker 133*6236dae4SAndroid Build Coastguard Worker # download 10MB files sequentially 134*6236dae4SAndroid Build Coastguard Worker @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests") 135*6236dae4SAndroid Build Coastguard Worker @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs") 136*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['h2', 'h3']) 137*6236dae4SAndroid Build Coastguard Worker def test_08_04b_download_10mb_sequential(self, env: Env, caddy: Caddy, 138*6236dae4SAndroid Build Coastguard Worker repeat, proto): 139*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3_curl(): 140*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported in curl") 141*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 142*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 itself crashes") 143*6236dae4SAndroid Build Coastguard Worker count = 20 144*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 145*6236dae4SAndroid Build Coastguard Worker urln = f'https://{env.domain1}:{caddy.port}/data10.data?[0-{count-1}]' 146*6236dae4SAndroid Build Coastguard Worker r = curl.http_download(urls=[urln], alpn_proto=proto) 147*6236dae4SAndroid Build Coastguard Worker r.check_response(count=count, http_status=200, connect_count=1) 148*6236dae4SAndroid Build Coastguard Worker 149*6236dae4SAndroid Build Coastguard Worker # download 10MB files parallel 150*6236dae4SAndroid Build Coastguard Worker @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests") 151*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 152*6236dae4SAndroid Build Coastguard Worker @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs") 153*6236dae4SAndroid Build Coastguard Worker def test_08_05_download_1mb_parallel(self, env: Env, caddy: Caddy, 154*6236dae4SAndroid Build Coastguard Worker repeat, proto): 155*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3_curl(): 156*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported in curl") 157*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 158*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 itself crashes") 159*6236dae4SAndroid Build Coastguard Worker if proto == 'http/1.1' and env.curl_uses_lib('mbedtls'): 160*6236dae4SAndroid Build Coastguard Worker pytest.skip("mbedtls 3.6.0 fails on 50 connections with: "\ 161*6236dae4SAndroid Build Coastguard Worker "ssl_handshake returned: (-0x7F00) SSL - Memory allocation failed") 162*6236dae4SAndroid Build Coastguard Worker count = 50 163*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 164*6236dae4SAndroid Build Coastguard Worker urln = f'https://{env.domain1}:{caddy.port}/data10.data?[0-{count-1}]' 165*6236dae4SAndroid Build Coastguard Worker r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[ 166*6236dae4SAndroid Build Coastguard Worker '--parallel' 167*6236dae4SAndroid Build Coastguard Worker ]) 168*6236dae4SAndroid Build Coastguard Worker r.check_response(count=count, http_status=200) 169*6236dae4SAndroid Build Coastguard Worker if proto == 'http/1.1': 170*6236dae4SAndroid Build Coastguard Worker # http/1.1 parallel transfers will open multiple connections 171*6236dae4SAndroid Build Coastguard Worker assert r.total_connects > 1, r.dump_logs() 172*6236dae4SAndroid Build Coastguard Worker else: 173*6236dae4SAndroid Build Coastguard Worker assert r.total_connects == 1, r.dump_logs() 174*6236dae4SAndroid Build Coastguard Worker 175*6236dae4SAndroid Build Coastguard Worker # post data parallel, check that they were echoed 176*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 177*6236dae4SAndroid Build Coastguard Worker def test_08_06_post_parallel(self, env: Env, httpd, caddy, repeat, proto): 178*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3(): 179*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported") 180*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 181*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 stalls here") 182*6236dae4SAndroid Build Coastguard Worker # limit since we use a separate connection in h1 183*6236dae4SAndroid Build Coastguard Worker count = 20 184*6236dae4SAndroid Build Coastguard Worker data = '0123456789' 185*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 186*6236dae4SAndroid Build Coastguard Worker url = f'https://{env.domain2}:{caddy.port}/curltest/echo?id=[0-{count-1}]' 187*6236dae4SAndroid Build Coastguard Worker r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, 188*6236dae4SAndroid Build Coastguard Worker extra_args=['--parallel']) 189*6236dae4SAndroid Build Coastguard Worker r.check_stats(count=count, http_status=200, exitcode=0) 190*6236dae4SAndroid Build Coastguard Worker for i in range(count): 191*6236dae4SAndroid Build Coastguard Worker respdata = open(curl.response_file(i)).readlines() 192*6236dae4SAndroid Build Coastguard Worker assert respdata == [data] 193*6236dae4SAndroid Build Coastguard Worker 194*6236dae4SAndroid Build Coastguard Worker # put large file, check that they length were echoed 195*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 196*6236dae4SAndroid Build Coastguard Worker def test_08_07_put_large(self, env: Env, httpd, caddy, repeat, proto): 197*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and not env.have_h3(): 198*6236dae4SAndroid Build Coastguard Worker pytest.skip("h3 not supported") 199*6236dae4SAndroid Build Coastguard Worker if proto == 'h3' and env.curl_uses_lib('msh3'): 200*6236dae4SAndroid Build Coastguard Worker pytest.skip("msh3 stalls here") 201*6236dae4SAndroid Build Coastguard Worker # limit since we use a separate connection in h1< 202*6236dae4SAndroid Build Coastguard Worker count = 1 203*6236dae4SAndroid Build Coastguard Worker fdata = os.path.join(env.gen_dir, 'data-10m') 204*6236dae4SAndroid Build Coastguard Worker curl = CurlClient(env=env) 205*6236dae4SAndroid Build Coastguard Worker url = f'https://{env.domain2}:{caddy.port}/curltest/put?id=[0-{count-1}]' 206*6236dae4SAndroid Build Coastguard Worker r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto) 207*6236dae4SAndroid Build Coastguard Worker exp_data = [f'{os.path.getsize(fdata)}'] 208*6236dae4SAndroid Build Coastguard Worker r.check_response(count=count, http_status=200) 209*6236dae4SAndroid Build Coastguard Worker for i in range(count): 210*6236dae4SAndroid Build Coastguard Worker respdata = open(curl.response_file(i)).readlines() 211*6236dae4SAndroid Build Coastguard Worker assert respdata == exp_data 212*6236dae4SAndroid Build Coastguard Worker 213*6236dae4SAndroid Build Coastguard Worker @pytest.mark.parametrize("proto", ['http/1.1', 'h2']) 214*6236dae4SAndroid Build Coastguard Worker def test_08_08_earlydata(self, env: Env, httpd, caddy, proto): 215*6236dae4SAndroid Build Coastguard Worker count = 2 216*6236dae4SAndroid Build Coastguard Worker docname = 'data10k.data' 217*6236dae4SAndroid Build Coastguard Worker url = f'https://{env.domain1}:{caddy.port}/{docname}' 218*6236dae4SAndroid Build Coastguard Worker client = LocalClient(name='hx-download', env=env) 219*6236dae4SAndroid Build Coastguard Worker if not client.exists(): 220*6236dae4SAndroid Build Coastguard Worker pytest.skip(f'example client not built: {client.name}') 221*6236dae4SAndroid Build Coastguard Worker r = client.run(args=[ 222*6236dae4SAndroid Build Coastguard Worker '-n', f'{count}', 223*6236dae4SAndroid Build Coastguard Worker '-e', # use TLS earlydata 224*6236dae4SAndroid Build Coastguard Worker '-f', # forbid reuse of connections 225*6236dae4SAndroid Build Coastguard Worker '-r', f'{env.domain1}:{caddy.port}:127.0.0.1', 226*6236dae4SAndroid Build Coastguard Worker '-V', proto, url 227*6236dae4SAndroid Build Coastguard Worker ]) 228*6236dae4SAndroid Build Coastguard Worker r.check_exit_code(0) 229*6236dae4SAndroid Build Coastguard Worker srcfile = os.path.join(caddy.docs_dir, docname) 230*6236dae4SAndroid Build Coastguard Worker self.check_downloads(client, srcfile, count) 231*6236dae4SAndroid Build Coastguard Worker earlydata = {} 232*6236dae4SAndroid Build Coastguard Worker for line in r.trace_lines: 233*6236dae4SAndroid Build Coastguard Worker m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line) 234*6236dae4SAndroid Build Coastguard Worker if m: 235*6236dae4SAndroid Build Coastguard Worker earlydata[int(m.group(1))] = int(m.group(2)) 236*6236dae4SAndroid Build Coastguard Worker # Caddy does not support early data 237*6236dae4SAndroid Build Coastguard Worker assert earlydata[0] == 0, f'{earlydata}' 238*6236dae4SAndroid Build Coastguard Worker assert earlydata[1] == 0, f'{earlydata}' 239*6236dae4SAndroid Build Coastguard Worker 240*6236dae4SAndroid Build Coastguard Worker def check_downloads(self, client, srcfile: str, count: int, 241*6236dae4SAndroid Build Coastguard Worker complete: bool = True): 242*6236dae4SAndroid Build Coastguard Worker for i in range(count): 243*6236dae4SAndroid Build Coastguard Worker dfile = client.download_file(i) 244*6236dae4SAndroid Build Coastguard Worker assert os.path.exists(dfile) 245*6236dae4SAndroid Build Coastguard Worker if complete and not filecmp.cmp(srcfile, dfile, shallow=False): 246*6236dae4SAndroid Build Coastguard Worker diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(), 247*6236dae4SAndroid Build Coastguard Worker b=open(dfile).readlines(), 248*6236dae4SAndroid Build Coastguard Worker fromfile=srcfile, 249*6236dae4SAndroid Build Coastguard Worker tofile=dfile, 250*6236dae4SAndroid Build Coastguard Worker n=1)) 251*6236dae4SAndroid Build Coastguard Worker assert False, f'download {dfile} differs:\n{diff}' 252