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 logging 28import os 29import pytest 30 31from testenv import Env, CurlClient, Caddy 32 33 34log = logging.getLogger(__name__) 35 36 37@pytest.mark.skipif(condition=not Env.has_caddy(), reason=f"missing caddy") 38@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL") 39class TestCaddy: 40 41 @pytest.fixture(autouse=True, scope='class') 42 def caddy(self, env): 43 caddy = Caddy(env=env) 44 assert caddy.start() 45 yield caddy 46 caddy.stop() 47 48 def _make_docs_file(self, docs_dir: str, fname: str, fsize: int): 49 fpath = os.path.join(docs_dir, fname) 50 data1k = 1024*'x' 51 flen = 0 52 with open(fpath, 'w') as fd: 53 while flen < fsize: 54 fd.write(data1k) 55 flen += len(data1k) 56 return flen 57 58 @pytest.fixture(autouse=True, scope='class') 59 def _class_scope(self, env, caddy): 60 self._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024*1024) 61 self._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5*1024*1024) 62 self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10*1024*1024) 63 self._make_docs_file(docs_dir=caddy.docs_dir, fname='data100.data', fsize=100*1024*1024) 64 env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024) 65 66 # download 1 file 67 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 68 def test_08_01_download_1(self, env: Env, caddy: Caddy, repeat, proto): 69 if proto == 'h3' and not env.have_h3_curl(): 70 pytest.skip("h3 not supported in curl") 71 if proto == 'h3' and env.curl_uses_lib('msh3'): 72 pytest.skip("msh3 itself crashes") 73 curl = CurlClient(env=env) 74 url = f'https://{env.domain1}:{caddy.port}/data.json' 75 r = curl.http_download(urls=[url], alpn_proto=proto) 76 r.check_response(count=1, http_status=200) 77 78 # download 1MB files sequentially 79 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 80 def test_08_02_download_1mb_sequential(self, env: Env, caddy: Caddy, 81 repeat, proto): 82 if proto == 'h3' and not env.have_h3_curl(): 83 pytest.skip("h3 not supported in curl") 84 if proto == 'h3' and env.curl_uses_lib('msh3'): 85 pytest.skip("msh3 itself crashes") 86 count = 50 87 curl = CurlClient(env=env) 88 urln = f'https://{env.domain1}:{caddy.port}/data1.data?[0-{count-1}]' 89 r = curl.http_download(urls=[urln], alpn_proto=proto) 90 r.check_response(count=count, http_status=200, connect_count=1) 91 92 # download 1MB files parallel 93 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 94 def test_08_03_download_1mb_parallel(self, env: Env, caddy: Caddy, 95 repeat, proto): 96 if proto == 'h3' and not env.have_h3_curl(): 97 pytest.skip("h3 not supported in curl") 98 if proto == 'h3' and env.curl_uses_lib('msh3'): 99 pytest.skip("msh3 itself crashes") 100 count = 20 101 curl = CurlClient(env=env) 102 urln = f'https://{env.domain1}:{caddy.port}/data1.data?[0-{count-1}]' 103 r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[ 104 '--parallel' 105 ]) 106 r.check_response(count=count, http_status=200) 107 if proto == 'http/1.1': 108 # http/1.1 parallel transfers will open multiple connections 109 assert r.total_connects > 1, r.dump_logs() 110 else: 111 assert r.total_connects == 1, r.dump_logs() 112 113 # download 5MB files sequentially 114 @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests") 115 @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs") 116 @pytest.mark.parametrize("proto", ['h2', 'h3']) 117 def test_08_04a_download_10mb_sequential(self, env: Env, caddy: Caddy, 118 repeat, proto): 119 if proto == 'h3' and not env.have_h3_curl(): 120 pytest.skip("h3 not supported in curl") 121 if proto == 'h3' and env.curl_uses_lib('msh3'): 122 pytest.skip("msh3 itself crashes") 123 count = 40 124 curl = CurlClient(env=env) 125 urln = f'https://{env.domain1}:{caddy.port}/data5.data?[0-{count-1}]' 126 r = curl.http_download(urls=[urln], alpn_proto=proto) 127 r.check_response(count=count, http_status=200, connect_count=1) 128 129 # download 10MB files sequentially 130 @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests") 131 @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs") 132 @pytest.mark.parametrize("proto", ['h2', 'h3']) 133 def test_08_04b_download_10mb_sequential(self, env: Env, caddy: Caddy, 134 repeat, proto): 135 if proto == 'h3' and not env.have_h3_curl(): 136 pytest.skip("h3 not supported in curl") 137 if proto == 'h3' and env.curl_uses_lib('msh3'): 138 pytest.skip("msh3 itself crashes") 139 count = 20 140 curl = CurlClient(env=env) 141 urln = f'https://{env.domain1}:{caddy.port}/data10.data?[0-{count-1}]' 142 r = curl.http_download(urls=[urln], alpn_proto=proto) 143 r.check_response(count=count, http_status=200, connect_count=1) 144 145 # download 10MB files parallel 146 @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests") 147 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 148 @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs") 149 def test_08_05_download_1mb_parallel(self, env: Env, caddy: Caddy, 150 repeat, proto): 151 if proto == 'h3' and not env.have_h3_curl(): 152 pytest.skip("h3 not supported in curl") 153 if proto == 'h3' and env.curl_uses_lib('msh3'): 154 pytest.skip("msh3 itself crashes") 155 if proto == 'http/1.1' and env.curl_uses_lib('mbedtls'): 156 pytest.skip("mbedtls 3.6.0 fails on 50 connections with: "\ 157 "ssl_handshake returned: (-0x7F00) SSL - Memory allocation failed") 158 count = 50 159 curl = CurlClient(env=env) 160 urln = f'https://{env.domain1}:{caddy.port}/data10.data?[0-{count-1}]' 161 r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[ 162 '--parallel' 163 ]) 164 r.check_response(count=count, http_status=200) 165 if proto == 'http/1.1': 166 # http/1.1 parallel transfers will open multiple connections 167 assert r.total_connects > 1, r.dump_logs() 168 else: 169 assert r.total_connects == 1, r.dump_logs() 170 171 # post data parallel, check that they were echoed 172 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 173 def test_08_06_post_parallel(self, env: Env, httpd, caddy, repeat, proto): 174 if proto == 'h3' and not env.have_h3(): 175 pytest.skip("h3 not supported") 176 if proto == 'h3' and env.curl_uses_lib('msh3'): 177 pytest.skip("msh3 stalls here") 178 # limit since we use a separate connection in h1 179 count = 20 180 data = '0123456789' 181 curl = CurlClient(env=env) 182 url = f'https://{env.domain2}:{caddy.port}/curltest/echo?id=[0-{count-1}]' 183 r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, 184 extra_args=['--parallel']) 185 r.check_stats(count=count, http_status=200, exitcode=0) 186 for i in range(0,count): 187 respdata = open(curl.response_file(i)).readlines() 188 assert respdata == [data] 189 190 # put large file, check that they length were echoed 191 @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) 192 def test_08_07_put_large(self, env: Env, httpd, caddy, repeat, proto): 193 if proto == 'h3' and not env.have_h3(): 194 pytest.skip("h3 not supported") 195 if proto == 'h3' and env.curl_uses_lib('msh3'): 196 pytest.skip("msh3 stalls here") 197 # limit since we use a separate connection in h1< 198 count = 1 199 fdata = os.path.join(env.gen_dir, 'data-10m') 200 curl = CurlClient(env=env) 201 url = f'https://{env.domain2}:{caddy.port}/curltest/put?id=[0-{count-1}]' 202 r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto) 203 exp_data = [f'{os.path.getsize(fdata)}'] 204 r.check_response(count=count, http_status=200) 205 for i in range(count): 206 respdata = open(curl.response_file(i)).readlines() 207 assert respdata == exp_data 208