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 json 28*6236dae4SAndroid Build Coastguard Workerimport logging 29*6236dae4SAndroid Build Coastguard Workerimport os 30*6236dae4SAndroid Build Coastguard Workerimport sys 31*6236dae4SAndroid Build Coastguard Workerimport time 32*6236dae4SAndroid Build Coastguard Workerfrom threading import Thread 33*6236dae4SAndroid Build Coastguard Worker 34*6236dae4SAndroid Build Coastguard Workerimport psutil 35*6236dae4SAndroid Build Coastguard Workerimport re 36*6236dae4SAndroid Build Coastguard Workerimport shutil 37*6236dae4SAndroid Build Coastguard Workerimport subprocess 38*6236dae4SAndroid Build Coastguard Workerfrom statistics import mean, fmean 39*6236dae4SAndroid Build Coastguard Workerfrom datetime import timedelta, datetime 40*6236dae4SAndroid Build Coastguard Workerfrom typing import List, Optional, Dict, Union, Any 41*6236dae4SAndroid Build Coastguard Workerfrom urllib.parse import urlparse 42*6236dae4SAndroid Build Coastguard Worker 43*6236dae4SAndroid Build Coastguard Workerfrom .env import Env 44*6236dae4SAndroid Build Coastguard Worker 45*6236dae4SAndroid Build Coastguard Worker 46*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__) 47*6236dae4SAndroid Build Coastguard Worker 48*6236dae4SAndroid Build Coastguard Worker 49*6236dae4SAndroid Build Coastguard Workerclass RunProfile: 50*6236dae4SAndroid Build Coastguard Worker 51*6236dae4SAndroid Build Coastguard Worker STAT_KEYS = ['cpu', 'rss', 'vsz'] 52*6236dae4SAndroid Build Coastguard Worker 53*6236dae4SAndroid Build Coastguard Worker @classmethod 54*6236dae4SAndroid Build Coastguard Worker def AverageStats(cls, profiles: List['RunProfile']): 55*6236dae4SAndroid Build Coastguard Worker avg = {} 56*6236dae4SAndroid Build Coastguard Worker stats = [p.stats for p in profiles] 57*6236dae4SAndroid Build Coastguard Worker for key in cls.STAT_KEYS: 58*6236dae4SAndroid Build Coastguard Worker vals = [s[key] for s in stats] 59*6236dae4SAndroid Build Coastguard Worker avg[key] = mean(vals) if len(vals) else 0.0 60*6236dae4SAndroid Build Coastguard Worker return avg 61*6236dae4SAndroid Build Coastguard Worker 62*6236dae4SAndroid Build Coastguard Worker def __init__(self, pid: int, started_at: datetime, run_dir): 63*6236dae4SAndroid Build Coastguard Worker self._pid = pid 64*6236dae4SAndroid Build Coastguard Worker self._started_at = started_at 65*6236dae4SAndroid Build Coastguard Worker self._duration = timedelta(seconds=0) 66*6236dae4SAndroid Build Coastguard Worker self._run_dir = run_dir 67*6236dae4SAndroid Build Coastguard Worker self._samples = [] 68*6236dae4SAndroid Build Coastguard Worker self._psu = None 69*6236dae4SAndroid Build Coastguard Worker self._stats = None 70*6236dae4SAndroid Build Coastguard Worker 71*6236dae4SAndroid Build Coastguard Worker @property 72*6236dae4SAndroid Build Coastguard Worker def duration(self) -> timedelta: 73*6236dae4SAndroid Build Coastguard Worker return self._duration 74*6236dae4SAndroid Build Coastguard Worker 75*6236dae4SAndroid Build Coastguard Worker @property 76*6236dae4SAndroid Build Coastguard Worker def stats(self) -> Optional[Dict[str,Any]]: 77*6236dae4SAndroid Build Coastguard Worker return self._stats 78*6236dae4SAndroid Build Coastguard Worker 79*6236dae4SAndroid Build Coastguard Worker def sample(self): 80*6236dae4SAndroid Build Coastguard Worker elapsed = datetime.now() - self._started_at 81*6236dae4SAndroid Build Coastguard Worker try: 82*6236dae4SAndroid Build Coastguard Worker if self._psu is None: 83*6236dae4SAndroid Build Coastguard Worker self._psu = psutil.Process(pid=self._pid) 84*6236dae4SAndroid Build Coastguard Worker mem = self._psu.memory_info() 85*6236dae4SAndroid Build Coastguard Worker self._samples.append({ 86*6236dae4SAndroid Build Coastguard Worker 'time': elapsed, 87*6236dae4SAndroid Build Coastguard Worker 'cpu': self._psu.cpu_percent(), 88*6236dae4SAndroid Build Coastguard Worker 'vsz': mem.vms, 89*6236dae4SAndroid Build Coastguard Worker 'rss': mem.rss, 90*6236dae4SAndroid Build Coastguard Worker }) 91*6236dae4SAndroid Build Coastguard Worker except psutil.NoSuchProcess: 92*6236dae4SAndroid Build Coastguard Worker pass 93*6236dae4SAndroid Build Coastguard Worker 94*6236dae4SAndroid Build Coastguard Worker def finish(self): 95*6236dae4SAndroid Build Coastguard Worker self._duration = datetime.now() - self._started_at 96*6236dae4SAndroid Build Coastguard Worker if len(self._samples) > 0: 97*6236dae4SAndroid Build Coastguard Worker weights = [s['time'].total_seconds() for s in self._samples] 98*6236dae4SAndroid Build Coastguard Worker self._stats = {} 99*6236dae4SAndroid Build Coastguard Worker for key in self.STAT_KEYS: 100*6236dae4SAndroid Build Coastguard Worker self._stats[key] = fmean([s[key] for s in self._samples], weights) 101*6236dae4SAndroid Build Coastguard Worker else: 102*6236dae4SAndroid Build Coastguard Worker self._stats = None 103*6236dae4SAndroid Build Coastguard Worker self._psu = None 104*6236dae4SAndroid Build Coastguard Worker 105*6236dae4SAndroid Build Coastguard Worker def __repr__(self): 106*6236dae4SAndroid Build Coastguard Worker return f'RunProfile[pid={self._pid}, '\ 107*6236dae4SAndroid Build Coastguard Worker f'duration={self.duration.total_seconds():.3f}s, '\ 108*6236dae4SAndroid Build Coastguard Worker f'stats={self.stats}]' 109*6236dae4SAndroid Build Coastguard Worker 110*6236dae4SAndroid Build Coastguard Worker 111*6236dae4SAndroid Build Coastguard Workerclass RunTcpDump: 112*6236dae4SAndroid Build Coastguard Worker 113*6236dae4SAndroid Build Coastguard Worker def __init__(self, env, run_dir): 114*6236dae4SAndroid Build Coastguard Worker self._env = env 115*6236dae4SAndroid Build Coastguard Worker self._run_dir = run_dir 116*6236dae4SAndroid Build Coastguard Worker self._proc = None 117*6236dae4SAndroid Build Coastguard Worker self._stdoutfile = os.path.join(self._run_dir, 'tcpdump.out') 118*6236dae4SAndroid Build Coastguard Worker self._stderrfile = os.path.join(self._run_dir, 'tcpdump.err') 119*6236dae4SAndroid Build Coastguard Worker 120*6236dae4SAndroid Build Coastguard Worker @property 121*6236dae4SAndroid Build Coastguard Worker def stats(self) -> Optional[List[str]]: 122*6236dae4SAndroid Build Coastguard Worker if self._proc: 123*6236dae4SAndroid Build Coastguard Worker raise Exception('tcpdump still running') 124*6236dae4SAndroid Build Coastguard Worker return [line 125*6236dae4SAndroid Build Coastguard Worker for line in open(self._stdoutfile) 126*6236dae4SAndroid Build Coastguard Worker if re.match(r'.* IP 127\.0\.0\.1\.\d+ [<>] 127\.0\.0\.1\.\d+:.*', line)] 127*6236dae4SAndroid Build Coastguard Worker 128*6236dae4SAndroid Build Coastguard Worker def stats_excluding(self, src_port) -> Optional[List[str]]: 129*6236dae4SAndroid Build Coastguard Worker if self._proc: 130*6236dae4SAndroid Build Coastguard Worker raise Exception('tcpdump still running') 131*6236dae4SAndroid Build Coastguard Worker return [line 132*6236dae4SAndroid Build Coastguard Worker for line in self.stats 133*6236dae4SAndroid Build Coastguard Worker if not re.match(r'.* IP 127\.0\.0\.1\.' + str(src_port) + ' >.*', line)] 134*6236dae4SAndroid Build Coastguard Worker 135*6236dae4SAndroid Build Coastguard Worker @property 136*6236dae4SAndroid Build Coastguard Worker def stderr(self) -> List[str]: 137*6236dae4SAndroid Build Coastguard Worker if self._proc: 138*6236dae4SAndroid Build Coastguard Worker raise Exception('tcpdump still running') 139*6236dae4SAndroid Build Coastguard Worker return open(self._stderrfile).readlines() 140*6236dae4SAndroid Build Coastguard Worker 141*6236dae4SAndroid Build Coastguard Worker def sample(self): 142*6236dae4SAndroid Build Coastguard Worker # not sure how to make that detection reliable for all platforms 143*6236dae4SAndroid Build Coastguard Worker local_if = 'lo0' if sys.platform.startswith('darwin') else 'lo' 144*6236dae4SAndroid Build Coastguard Worker try: 145*6236dae4SAndroid Build Coastguard Worker tcpdump = self._env.tcpdump() 146*6236dae4SAndroid Build Coastguard Worker if tcpdump is None: 147*6236dae4SAndroid Build Coastguard Worker raise Exception('tcpdump not available') 148*6236dae4SAndroid Build Coastguard Worker # look with tcpdump for TCP RST packets which indicate 149*6236dae4SAndroid Build Coastguard Worker # we did not shut down connections cleanly 150*6236dae4SAndroid Build Coastguard Worker args = [] 151*6236dae4SAndroid Build Coastguard Worker # at least on Linux, we need root permissions to run tcpdump 152*6236dae4SAndroid Build Coastguard Worker if sys.platform.startswith('linux'): 153*6236dae4SAndroid Build Coastguard Worker args.append('sudo') 154*6236dae4SAndroid Build Coastguard Worker args.extend([ 155*6236dae4SAndroid Build Coastguard Worker tcpdump, '-i', local_if, '-n', 'tcp[tcpflags] & (tcp-rst)!=0' 156*6236dae4SAndroid Build Coastguard Worker ]) 157*6236dae4SAndroid Build Coastguard Worker with open(self._stdoutfile, 'w') as cout, open(self._stderrfile, 'w') as cerr: 158*6236dae4SAndroid Build Coastguard Worker self._proc = subprocess.Popen(args, stdout=cout, stderr=cerr, 159*6236dae4SAndroid Build Coastguard Worker text=True, cwd=self._run_dir, 160*6236dae4SAndroid Build Coastguard Worker shell=False) 161*6236dae4SAndroid Build Coastguard Worker assert self._proc 162*6236dae4SAndroid Build Coastguard Worker assert self._proc.returncode is None 163*6236dae4SAndroid Build Coastguard Worker while self._proc: 164*6236dae4SAndroid Build Coastguard Worker try: 165*6236dae4SAndroid Build Coastguard Worker self._proc.wait(timeout=1) 166*6236dae4SAndroid Build Coastguard Worker except subprocess.TimeoutExpired: 167*6236dae4SAndroid Build Coastguard Worker pass 168*6236dae4SAndroid Build Coastguard Worker except Exception: 169*6236dae4SAndroid Build Coastguard Worker log.exception('Tcpdump') 170*6236dae4SAndroid Build Coastguard Worker 171*6236dae4SAndroid Build Coastguard Worker def start(self): 172*6236dae4SAndroid Build Coastguard Worker def do_sample(): 173*6236dae4SAndroid Build Coastguard Worker self.sample() 174*6236dae4SAndroid Build Coastguard Worker t = Thread(target=do_sample) 175*6236dae4SAndroid Build Coastguard Worker t.start() 176*6236dae4SAndroid Build Coastguard Worker 177*6236dae4SAndroid Build Coastguard Worker def finish(self): 178*6236dae4SAndroid Build Coastguard Worker if self._proc: 179*6236dae4SAndroid Build Coastguard Worker time.sleep(1) 180*6236dae4SAndroid Build Coastguard Worker self._proc.terminate() 181*6236dae4SAndroid Build Coastguard Worker self._proc = None 182*6236dae4SAndroid Build Coastguard Worker 183*6236dae4SAndroid Build Coastguard Worker 184*6236dae4SAndroid Build Coastguard Workerclass ExecResult: 185*6236dae4SAndroid Build Coastguard Worker 186*6236dae4SAndroid Build Coastguard Worker def __init__(self, args: List[str], exit_code: int, 187*6236dae4SAndroid Build Coastguard Worker stdout: List[str], stderr: List[str], 188*6236dae4SAndroid Build Coastguard Worker duration: Optional[timedelta] = None, 189*6236dae4SAndroid Build Coastguard Worker with_stats: bool = False, 190*6236dae4SAndroid Build Coastguard Worker exception: Optional[str] = None, 191*6236dae4SAndroid Build Coastguard Worker profile: Optional[RunProfile] = None, 192*6236dae4SAndroid Build Coastguard Worker tcpdump: Optional[RunTcpDump] = None): 193*6236dae4SAndroid Build Coastguard Worker self._args = args 194*6236dae4SAndroid Build Coastguard Worker self._exit_code = exit_code 195*6236dae4SAndroid Build Coastguard Worker self._exception = exception 196*6236dae4SAndroid Build Coastguard Worker self._stdout = stdout 197*6236dae4SAndroid Build Coastguard Worker self._stderr = stderr 198*6236dae4SAndroid Build Coastguard Worker self._profile = profile 199*6236dae4SAndroid Build Coastguard Worker self._tcpdump = tcpdump 200*6236dae4SAndroid Build Coastguard Worker self._duration = duration if duration is not None else timedelta() 201*6236dae4SAndroid Build Coastguard Worker self._response = None 202*6236dae4SAndroid Build Coastguard Worker self._responses = [] 203*6236dae4SAndroid Build Coastguard Worker self._results = {} 204*6236dae4SAndroid Build Coastguard Worker self._assets = [] 205*6236dae4SAndroid Build Coastguard Worker self._stats = [] 206*6236dae4SAndroid Build Coastguard Worker self._json_out = None 207*6236dae4SAndroid Build Coastguard Worker self._with_stats = with_stats 208*6236dae4SAndroid Build Coastguard Worker if with_stats: 209*6236dae4SAndroid Build Coastguard Worker self._parse_stats() 210*6236dae4SAndroid Build Coastguard Worker else: 211*6236dae4SAndroid Build Coastguard Worker # noinspection PyBroadException 212*6236dae4SAndroid Build Coastguard Worker try: 213*6236dae4SAndroid Build Coastguard Worker out = ''.join(self._stdout) 214*6236dae4SAndroid Build Coastguard Worker self._json_out = json.loads(out) 215*6236dae4SAndroid Build Coastguard Worker except: # noqa: E722 216*6236dae4SAndroid Build Coastguard Worker pass 217*6236dae4SAndroid Build Coastguard Worker 218*6236dae4SAndroid Build Coastguard Worker def __repr__(self): 219*6236dae4SAndroid Build Coastguard Worker return f"ExecResult[code={self.exit_code}, exception={self._exception}, "\ 220*6236dae4SAndroid Build Coastguard Worker f"args={self._args}, stdout={self._stdout}, stderr={self._stderr}]" 221*6236dae4SAndroid Build Coastguard Worker 222*6236dae4SAndroid Build Coastguard Worker def _parse_stats(self): 223*6236dae4SAndroid Build Coastguard Worker self._stats = [] 224*6236dae4SAndroid Build Coastguard Worker for line in self._stdout: 225*6236dae4SAndroid Build Coastguard Worker try: 226*6236dae4SAndroid Build Coastguard Worker self._stats.append(json.loads(line)) 227*6236dae4SAndroid Build Coastguard Worker # TODO: specify specific exceptions here 228*6236dae4SAndroid Build Coastguard Worker except: # noqa: E722 229*6236dae4SAndroid Build Coastguard Worker log.exception(f'not a JSON stat: {line}') 230*6236dae4SAndroid Build Coastguard Worker break 231*6236dae4SAndroid Build Coastguard Worker 232*6236dae4SAndroid Build Coastguard Worker @property 233*6236dae4SAndroid Build Coastguard Worker def exit_code(self) -> int: 234*6236dae4SAndroid Build Coastguard Worker return self._exit_code 235*6236dae4SAndroid Build Coastguard Worker 236*6236dae4SAndroid Build Coastguard Worker @property 237*6236dae4SAndroid Build Coastguard Worker def args(self) -> List[str]: 238*6236dae4SAndroid Build Coastguard Worker return self._args 239*6236dae4SAndroid Build Coastguard Worker 240*6236dae4SAndroid Build Coastguard Worker @property 241*6236dae4SAndroid Build Coastguard Worker def outraw(self) -> bytes: 242*6236dae4SAndroid Build Coastguard Worker return ''.join(self._stdout).encode() 243*6236dae4SAndroid Build Coastguard Worker 244*6236dae4SAndroid Build Coastguard Worker @property 245*6236dae4SAndroid Build Coastguard Worker def stdout(self) -> str: 246*6236dae4SAndroid Build Coastguard Worker return ''.join(self._stdout) 247*6236dae4SAndroid Build Coastguard Worker 248*6236dae4SAndroid Build Coastguard Worker @property 249*6236dae4SAndroid Build Coastguard Worker def json(self) -> Optional[Dict]: 250*6236dae4SAndroid Build Coastguard Worker """Output as JSON dictionary or None if not parseable.""" 251*6236dae4SAndroid Build Coastguard Worker return self._json_out 252*6236dae4SAndroid Build Coastguard Worker 253*6236dae4SAndroid Build Coastguard Worker @property 254*6236dae4SAndroid Build Coastguard Worker def stderr(self) -> str: 255*6236dae4SAndroid Build Coastguard Worker return ''.join(self._stderr) 256*6236dae4SAndroid Build Coastguard Worker 257*6236dae4SAndroid Build Coastguard Worker @property 258*6236dae4SAndroid Build Coastguard Worker def trace_lines(self) -> List[str]: 259*6236dae4SAndroid Build Coastguard Worker return self._stderr 260*6236dae4SAndroid Build Coastguard Worker 261*6236dae4SAndroid Build Coastguard Worker @property 262*6236dae4SAndroid Build Coastguard Worker def duration(self) -> timedelta: 263*6236dae4SAndroid Build Coastguard Worker return self._duration 264*6236dae4SAndroid Build Coastguard Worker 265*6236dae4SAndroid Build Coastguard Worker @property 266*6236dae4SAndroid Build Coastguard Worker def profile(self) -> Optional[RunProfile]: 267*6236dae4SAndroid Build Coastguard Worker return self._profile 268*6236dae4SAndroid Build Coastguard Worker 269*6236dae4SAndroid Build Coastguard Worker @property 270*6236dae4SAndroid Build Coastguard Worker def tcpdump(self) -> Optional[RunTcpDump]: 271*6236dae4SAndroid Build Coastguard Worker return self._tcpdump 272*6236dae4SAndroid Build Coastguard Worker 273*6236dae4SAndroid Build Coastguard Worker @property 274*6236dae4SAndroid Build Coastguard Worker def response(self) -> Optional[Dict]: 275*6236dae4SAndroid Build Coastguard Worker return self._response 276*6236dae4SAndroid Build Coastguard Worker 277*6236dae4SAndroid Build Coastguard Worker @property 278*6236dae4SAndroid Build Coastguard Worker def responses(self) -> List[Dict]: 279*6236dae4SAndroid Build Coastguard Worker return self._responses 280*6236dae4SAndroid Build Coastguard Worker 281*6236dae4SAndroid Build Coastguard Worker @property 282*6236dae4SAndroid Build Coastguard Worker def results(self) -> Dict: 283*6236dae4SAndroid Build Coastguard Worker return self._results 284*6236dae4SAndroid Build Coastguard Worker 285*6236dae4SAndroid Build Coastguard Worker @property 286*6236dae4SAndroid Build Coastguard Worker def assets(self) -> List: 287*6236dae4SAndroid Build Coastguard Worker return self._assets 288*6236dae4SAndroid Build Coastguard Worker 289*6236dae4SAndroid Build Coastguard Worker @property 290*6236dae4SAndroid Build Coastguard Worker def with_stats(self) -> bool: 291*6236dae4SAndroid Build Coastguard Worker return self._with_stats 292*6236dae4SAndroid Build Coastguard Worker 293*6236dae4SAndroid Build Coastguard Worker @property 294*6236dae4SAndroid Build Coastguard Worker def stats(self) -> List: 295*6236dae4SAndroid Build Coastguard Worker return self._stats 296*6236dae4SAndroid Build Coastguard Worker 297*6236dae4SAndroid Build Coastguard Worker @property 298*6236dae4SAndroid Build Coastguard Worker def total_connects(self) -> Optional[int]: 299*6236dae4SAndroid Build Coastguard Worker if len(self.stats): 300*6236dae4SAndroid Build Coastguard Worker n = 0 301*6236dae4SAndroid Build Coastguard Worker for stat in self.stats: 302*6236dae4SAndroid Build Coastguard Worker n += stat['num_connects'] 303*6236dae4SAndroid Build Coastguard Worker return n 304*6236dae4SAndroid Build Coastguard Worker return None 305*6236dae4SAndroid Build Coastguard Worker 306*6236dae4SAndroid Build Coastguard Worker def add_response(self, resp: Dict): 307*6236dae4SAndroid Build Coastguard Worker self._response = resp 308*6236dae4SAndroid Build Coastguard Worker self._responses.append(resp) 309*6236dae4SAndroid Build Coastguard Worker 310*6236dae4SAndroid Build Coastguard Worker def add_results(self, results: Dict): 311*6236dae4SAndroid Build Coastguard Worker self._results.update(results) 312*6236dae4SAndroid Build Coastguard Worker if 'response' in results: 313*6236dae4SAndroid Build Coastguard Worker self.add_response(results['response']) 314*6236dae4SAndroid Build Coastguard Worker 315*6236dae4SAndroid Build Coastguard Worker def add_assets(self, assets: List): 316*6236dae4SAndroid Build Coastguard Worker self._assets.extend(assets) 317*6236dae4SAndroid Build Coastguard Worker 318*6236dae4SAndroid Build Coastguard Worker def check_exit_code(self, code: Union[int, bool]): 319*6236dae4SAndroid Build Coastguard Worker if code is True: 320*6236dae4SAndroid Build Coastguard Worker assert self.exit_code == 0, f'expected exit code {code}, '\ 321*6236dae4SAndroid Build Coastguard Worker f'got {self.exit_code}\n{self.dump_logs()}' 322*6236dae4SAndroid Build Coastguard Worker elif code is False: 323*6236dae4SAndroid Build Coastguard Worker assert self.exit_code != 0, f'expected exit code {code}, '\ 324*6236dae4SAndroid Build Coastguard Worker f'got {self.exit_code}\n{self.dump_logs()}' 325*6236dae4SAndroid Build Coastguard Worker else: 326*6236dae4SAndroid Build Coastguard Worker assert self.exit_code == code, f'expected exit code {code}, '\ 327*6236dae4SAndroid Build Coastguard Worker f'got {self.exit_code}\n{self.dump_logs()}' 328*6236dae4SAndroid Build Coastguard Worker 329*6236dae4SAndroid Build Coastguard Worker def check_response(self, http_status: Optional[int] = 200, 330*6236dae4SAndroid Build Coastguard Worker count: Optional[int] = 1, 331*6236dae4SAndroid Build Coastguard Worker protocol: Optional[str] = None, 332*6236dae4SAndroid Build Coastguard Worker exitcode: Optional[int] = 0, 333*6236dae4SAndroid Build Coastguard Worker connect_count: Optional[int] = None): 334*6236dae4SAndroid Build Coastguard Worker if exitcode: 335*6236dae4SAndroid Build Coastguard Worker self.check_exit_code(exitcode) 336*6236dae4SAndroid Build Coastguard Worker if self.with_stats and isinstance(exitcode, int): 337*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.stats): 338*6236dae4SAndroid Build Coastguard Worker if 'exitcode' in x: 339*6236dae4SAndroid Build Coastguard Worker assert int(x['exitcode']) == exitcode, \ 340*6236dae4SAndroid Build Coastguard Worker f'response #{idx} exitcode: expected {exitcode}, '\ 341*6236dae4SAndroid Build Coastguard Worker f'got {x["exitcode"]}\n{self.dump_logs()}' 342*6236dae4SAndroid Build Coastguard Worker 343*6236dae4SAndroid Build Coastguard Worker if self.with_stats: 344*6236dae4SAndroid Build Coastguard Worker assert len(self.stats) == count, \ 345*6236dae4SAndroid Build Coastguard Worker f'response count: expected {count}, ' \ 346*6236dae4SAndroid Build Coastguard Worker f'got {len(self.stats)}\n{self.dump_logs()}' 347*6236dae4SAndroid Build Coastguard Worker else: 348*6236dae4SAndroid Build Coastguard Worker assert len(self.responses) == count, \ 349*6236dae4SAndroid Build Coastguard Worker f'response count: expected {count}, ' \ 350*6236dae4SAndroid Build Coastguard Worker f'got {len(self.responses)}\n{self.dump_logs()}' 351*6236dae4SAndroid Build Coastguard Worker if http_status is not None: 352*6236dae4SAndroid Build Coastguard Worker if self.with_stats: 353*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.stats): 354*6236dae4SAndroid Build Coastguard Worker assert 'http_code' in x, \ 355*6236dae4SAndroid Build Coastguard Worker f'response #{idx} reports no http_code\n{self.dump_stat(x)}' 356*6236dae4SAndroid Build Coastguard Worker assert x['http_code'] == http_status, \ 357*6236dae4SAndroid Build Coastguard Worker f'response #{idx} http_code: expected {http_status}, '\ 358*6236dae4SAndroid Build Coastguard Worker f'got {x["http_code"]}\n{self.dump_stat(x)}' 359*6236dae4SAndroid Build Coastguard Worker else: 360*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.responses): 361*6236dae4SAndroid Build Coastguard Worker assert x['status'] == http_status, \ 362*6236dae4SAndroid Build Coastguard Worker f'response #{idx} status: expected {http_status},'\ 363*6236dae4SAndroid Build Coastguard Worker f'got {x["status"]}\n{self.dump_stat(x)}' 364*6236dae4SAndroid Build Coastguard Worker if protocol is not None: 365*6236dae4SAndroid Build Coastguard Worker if self.with_stats: 366*6236dae4SAndroid Build Coastguard Worker http_version = None 367*6236dae4SAndroid Build Coastguard Worker if protocol == 'HTTP/1.1': 368*6236dae4SAndroid Build Coastguard Worker http_version = '1.1' 369*6236dae4SAndroid Build Coastguard Worker elif protocol == 'HTTP/2': 370*6236dae4SAndroid Build Coastguard Worker http_version = '2' 371*6236dae4SAndroid Build Coastguard Worker elif protocol == 'HTTP/3': 372*6236dae4SAndroid Build Coastguard Worker http_version = '3' 373*6236dae4SAndroid Build Coastguard Worker if http_version is not None: 374*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.stats): 375*6236dae4SAndroid Build Coastguard Worker assert x['http_version'] == http_version, \ 376*6236dae4SAndroid Build Coastguard Worker f'response #{idx} protocol: expected http/{http_version},' \ 377*6236dae4SAndroid Build Coastguard Worker f'got version {x["http_version"]}\n{self.dump_stat(x)}' 378*6236dae4SAndroid Build Coastguard Worker else: 379*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.responses): 380*6236dae4SAndroid Build Coastguard Worker assert x['protocol'] == protocol, \ 381*6236dae4SAndroid Build Coastguard Worker f'response #{idx} protocol: expected {protocol},'\ 382*6236dae4SAndroid Build Coastguard Worker f'got {x["protocol"]}\n{self.dump_logs()}' 383*6236dae4SAndroid Build Coastguard Worker if connect_count is not None: 384*6236dae4SAndroid Build Coastguard Worker assert self.total_connects == connect_count, \ 385*6236dae4SAndroid Build Coastguard Worker f'expected {connect_count}, but {self.total_connects} '\ 386*6236dae4SAndroid Build Coastguard Worker f'were made\n{self.dump_logs()}' 387*6236dae4SAndroid Build Coastguard Worker 388*6236dae4SAndroid Build Coastguard Worker def check_stats(self, count: int, http_status: Optional[int] = None, 389*6236dae4SAndroid Build Coastguard Worker exitcode: Optional[int] = None, 390*6236dae4SAndroid Build Coastguard Worker remote_port: Optional[int] = None, 391*6236dae4SAndroid Build Coastguard Worker remote_ip: Optional[str] = None): 392*6236dae4SAndroid Build Coastguard Worker if exitcode is None: 393*6236dae4SAndroid Build Coastguard Worker self.check_exit_code(0) 394*6236dae4SAndroid Build Coastguard Worker assert len(self.stats) == count, \ 395*6236dae4SAndroid Build Coastguard Worker f'stats count: expected {count}, got {len(self.stats)}\n{self.dump_logs()}' 396*6236dae4SAndroid Build Coastguard Worker if http_status is not None: 397*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.stats): 398*6236dae4SAndroid Build Coastguard Worker assert 'http_code' in x, \ 399*6236dae4SAndroid Build Coastguard Worker f'status #{idx} reports no http_code\n{self.dump_stat(x)}' 400*6236dae4SAndroid Build Coastguard Worker assert x['http_code'] == http_status, \ 401*6236dae4SAndroid Build Coastguard Worker f'status #{idx} http_code: expected {http_status}, '\ 402*6236dae4SAndroid Build Coastguard Worker f'got {x["http_code"]}\n{self.dump_stat(x)}' 403*6236dae4SAndroid Build Coastguard Worker if exitcode is not None: 404*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.stats): 405*6236dae4SAndroid Build Coastguard Worker if 'exitcode' in x: 406*6236dae4SAndroid Build Coastguard Worker assert x['exitcode'] == exitcode, \ 407*6236dae4SAndroid Build Coastguard Worker f'status #{idx} exitcode: expected {exitcode}, '\ 408*6236dae4SAndroid Build Coastguard Worker f'got {x["exitcode"]}\n{self.dump_stat(x)}' 409*6236dae4SAndroid Build Coastguard Worker if remote_port is not None: 410*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.stats): 411*6236dae4SAndroid Build Coastguard Worker assert 'remote_port' in x, f'remote_port missing\n{self.dump_stat(x)}' 412*6236dae4SAndroid Build Coastguard Worker assert x['remote_port'] == remote_port, \ 413*6236dae4SAndroid Build Coastguard Worker f'status #{idx} remote_port: expected {remote_port}, '\ 414*6236dae4SAndroid Build Coastguard Worker f'got {x["remote_port"]}\n{self.dump_stat(x)}' 415*6236dae4SAndroid Build Coastguard Worker if remote_ip is not None: 416*6236dae4SAndroid Build Coastguard Worker for idx, x in enumerate(self.stats): 417*6236dae4SAndroid Build Coastguard Worker assert 'remote_ip' in x, f'remote_ip missing\n{self.dump_stat(x)}' 418*6236dae4SAndroid Build Coastguard Worker assert x['remote_ip'] == remote_ip, \ 419*6236dae4SAndroid Build Coastguard Worker f'status #{idx} remote_ip: expected {remote_ip}, '\ 420*6236dae4SAndroid Build Coastguard Worker f'got {x["remote_ip"]}\n{self.dump_stat(x)}' 421*6236dae4SAndroid Build Coastguard Worker 422*6236dae4SAndroid Build Coastguard Worker def dump_logs(self): 423*6236dae4SAndroid Build Coastguard Worker lines = ['>>--stdout ----------------------------------------------\n'] 424*6236dae4SAndroid Build Coastguard Worker lines.extend(self._stdout) 425*6236dae4SAndroid Build Coastguard Worker lines.append('>>--stderr ----------------------------------------------\n') 426*6236dae4SAndroid Build Coastguard Worker lines.extend(self._stderr) 427*6236dae4SAndroid Build Coastguard Worker lines.append('<<-------------------------------------------------------\n') 428*6236dae4SAndroid Build Coastguard Worker return ''.join(lines) 429*6236dae4SAndroid Build Coastguard Worker 430*6236dae4SAndroid Build Coastguard Worker def dump_stat(self, x): 431*6236dae4SAndroid Build Coastguard Worker lines = [ 432*6236dae4SAndroid Build Coastguard Worker 'json stat from curl:', 433*6236dae4SAndroid Build Coastguard Worker json.JSONEncoder(indent=2).encode(x), 434*6236dae4SAndroid Build Coastguard Worker ] 435*6236dae4SAndroid Build Coastguard Worker if 'xfer_id' in x: 436*6236dae4SAndroid Build Coastguard Worker xfer_id = x['xfer_id'] 437*6236dae4SAndroid Build Coastguard Worker lines.append(f'>>--xfer {xfer_id} trace:\n') 438*6236dae4SAndroid Build Coastguard Worker lines.extend(self.xfer_trace_for(xfer_id)) 439*6236dae4SAndroid Build Coastguard Worker else: 440*6236dae4SAndroid Build Coastguard Worker lines.append('>>--full trace-------------------------------------------\n') 441*6236dae4SAndroid Build Coastguard Worker lines.extend(self._stderr) 442*6236dae4SAndroid Build Coastguard Worker lines.append('<<-------------------------------------------------------\n') 443*6236dae4SAndroid Build Coastguard Worker return ''.join(lines) 444*6236dae4SAndroid Build Coastguard Worker 445*6236dae4SAndroid Build Coastguard Worker def xfer_trace_for(self, xfer_id) -> List[str]: 446*6236dae4SAndroid Build Coastguard Worker pat = re.compile(f'^[^[]* \\[{xfer_id}-.*$') 447*6236dae4SAndroid Build Coastguard Worker return [line for line in self._stderr if pat.match(line)] 448*6236dae4SAndroid Build Coastguard Worker 449*6236dae4SAndroid Build Coastguard Worker 450*6236dae4SAndroid Build Coastguard Workerclass CurlClient: 451*6236dae4SAndroid Build Coastguard Worker 452*6236dae4SAndroid Build Coastguard Worker ALPN_ARG = { 453*6236dae4SAndroid Build Coastguard Worker 'http/0.9': '--http0.9', 454*6236dae4SAndroid Build Coastguard Worker 'http/1.0': '--http1.0', 455*6236dae4SAndroid Build Coastguard Worker 'http/1.1': '--http1.1', 456*6236dae4SAndroid Build Coastguard Worker 'h2': '--http2', 457*6236dae4SAndroid Build Coastguard Worker 'h2c': '--http2', 458*6236dae4SAndroid Build Coastguard Worker 'h3': '--http3-only', 459*6236dae4SAndroid Build Coastguard Worker } 460*6236dae4SAndroid Build Coastguard Worker 461*6236dae4SAndroid Build Coastguard Worker def __init__(self, env: Env, 462*6236dae4SAndroid Build Coastguard Worker run_dir: Optional[str] = None, 463*6236dae4SAndroid Build Coastguard Worker timeout: Optional[float] = None, 464*6236dae4SAndroid Build Coastguard Worker silent: bool = False, 465*6236dae4SAndroid Build Coastguard Worker run_env: Optional[Dict[str, str]] = None, 466*6236dae4SAndroid Build Coastguard Worker server_addr: Optional[str] = None): 467*6236dae4SAndroid Build Coastguard Worker self.env = env 468*6236dae4SAndroid Build Coastguard Worker self._timeout = timeout if timeout else env.test_timeout 469*6236dae4SAndroid Build Coastguard Worker self._curl = os.environ['CURL'] if 'CURL' in os.environ else env.curl 470*6236dae4SAndroid Build Coastguard Worker self._run_dir = run_dir if run_dir else os.path.join(env.gen_dir, 'curl') 471*6236dae4SAndroid Build Coastguard Worker self._stdoutfile = f'{self._run_dir}/curl.stdout' 472*6236dae4SAndroid Build Coastguard Worker self._stderrfile = f'{self._run_dir}/curl.stderr' 473*6236dae4SAndroid Build Coastguard Worker self._headerfile = f'{self._run_dir}/curl.headers' 474*6236dae4SAndroid Build Coastguard Worker self._log_path = f'{self._run_dir}/curl.log' 475*6236dae4SAndroid Build Coastguard Worker self._silent = silent 476*6236dae4SAndroid Build Coastguard Worker self._run_env = run_env 477*6236dae4SAndroid Build Coastguard Worker self._server_addr = server_addr if server_addr else '127.0.0.1' 478*6236dae4SAndroid Build Coastguard Worker self._rmrf(self._run_dir) 479*6236dae4SAndroid Build Coastguard Worker self._mkpath(self._run_dir) 480*6236dae4SAndroid Build Coastguard Worker 481*6236dae4SAndroid Build Coastguard Worker @property 482*6236dae4SAndroid Build Coastguard Worker def run_dir(self) -> str: 483*6236dae4SAndroid Build Coastguard Worker return self._run_dir 484*6236dae4SAndroid Build Coastguard Worker 485*6236dae4SAndroid Build Coastguard Worker def download_file(self, i: int) -> str: 486*6236dae4SAndroid Build Coastguard Worker return os.path.join(self.run_dir, f'download_{i}.data') 487*6236dae4SAndroid Build Coastguard Worker 488*6236dae4SAndroid Build Coastguard Worker def _rmf(self, path): 489*6236dae4SAndroid Build Coastguard Worker if os.path.exists(path): 490*6236dae4SAndroid Build Coastguard Worker return os.remove(path) 491*6236dae4SAndroid Build Coastguard Worker 492*6236dae4SAndroid Build Coastguard Worker def _rmrf(self, path): 493*6236dae4SAndroid Build Coastguard Worker if os.path.exists(path): 494*6236dae4SAndroid Build Coastguard Worker return shutil.rmtree(path) 495*6236dae4SAndroid Build Coastguard Worker 496*6236dae4SAndroid Build Coastguard Worker def _mkpath(self, path): 497*6236dae4SAndroid Build Coastguard Worker if not os.path.exists(path): 498*6236dae4SAndroid Build Coastguard Worker return os.makedirs(path) 499*6236dae4SAndroid Build Coastguard Worker 500*6236dae4SAndroid Build Coastguard Worker def get_proxy_args(self, proto: str = 'http/1.1', 501*6236dae4SAndroid Build Coastguard Worker proxys: bool = True, tunnel: bool = False, 502*6236dae4SAndroid Build Coastguard Worker use_ip: bool = False): 503*6236dae4SAndroid Build Coastguard Worker proxy_name = self._server_addr if use_ip else self.env.proxy_domain 504*6236dae4SAndroid Build Coastguard Worker if proxys: 505*6236dae4SAndroid Build Coastguard Worker pport = self.env.pts_port(proto) if tunnel else self.env.proxys_port 506*6236dae4SAndroid Build Coastguard Worker xargs = [ 507*6236dae4SAndroid Build Coastguard Worker '--proxy', f'https://{proxy_name}:{pport}/', 508*6236dae4SAndroid Build Coastguard Worker '--resolve', f'{proxy_name}:{pport}:{self._server_addr}', 509*6236dae4SAndroid Build Coastguard Worker '--proxy-cacert', self.env.ca.cert_file, 510*6236dae4SAndroid Build Coastguard Worker ] 511*6236dae4SAndroid Build Coastguard Worker if proto == 'h2': 512*6236dae4SAndroid Build Coastguard Worker xargs.append('--proxy-http2') 513*6236dae4SAndroid Build Coastguard Worker else: 514*6236dae4SAndroid Build Coastguard Worker xargs = [ 515*6236dae4SAndroid Build Coastguard Worker '--proxy', f'http://{proxy_name}:{self.env.proxy_port}/', 516*6236dae4SAndroid Build Coastguard Worker '--resolve', f'{proxy_name}:{self.env.proxy_port}:{self._server_addr}', 517*6236dae4SAndroid Build Coastguard Worker ] 518*6236dae4SAndroid Build Coastguard Worker if tunnel: 519*6236dae4SAndroid Build Coastguard Worker xargs.append('--proxytunnel') 520*6236dae4SAndroid Build Coastguard Worker return xargs 521*6236dae4SAndroid Build Coastguard Worker 522*6236dae4SAndroid Build Coastguard Worker def http_get(self, url: str, extra_args: Optional[List[str]] = None, 523*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 524*6236dae4SAndroid Build Coastguard Worker def_tracing: bool = True, 525*6236dae4SAndroid Build Coastguard Worker with_stats: bool = False, 526*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 527*6236dae4SAndroid Build Coastguard Worker with_tcpdump: bool = False): 528*6236dae4SAndroid Build Coastguard Worker return self._raw(url, options=extra_args, 529*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 530*6236dae4SAndroid Build Coastguard Worker alpn_proto=alpn_proto, 531*6236dae4SAndroid Build Coastguard Worker def_tracing=def_tracing, 532*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile, 533*6236dae4SAndroid Build Coastguard Worker with_tcpdump=with_tcpdump) 534*6236dae4SAndroid Build Coastguard Worker 535*6236dae4SAndroid Build Coastguard Worker def http_download(self, urls: List[str], 536*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 537*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 538*6236dae4SAndroid Build Coastguard Worker with_headers: bool = False, 539*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 540*6236dae4SAndroid Build Coastguard Worker with_tcpdump: bool = False, 541*6236dae4SAndroid Build Coastguard Worker no_save: bool = False, 542*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 543*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 544*6236dae4SAndroid Build Coastguard Worker extra_args = [] 545*6236dae4SAndroid Build Coastguard Worker if no_save: 546*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 547*6236dae4SAndroid Build Coastguard Worker '-o', '/dev/null', 548*6236dae4SAndroid Build Coastguard Worker ]) 549*6236dae4SAndroid Build Coastguard Worker else: 550*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 551*6236dae4SAndroid Build Coastguard Worker '-o', 'download_#1.data', 552*6236dae4SAndroid Build Coastguard Worker ]) 553*6236dae4SAndroid Build Coastguard Worker # remove any existing ones 554*6236dae4SAndroid Build Coastguard Worker for i in range(100): 555*6236dae4SAndroid Build Coastguard Worker self._rmf(self.download_file(i)) 556*6236dae4SAndroid Build Coastguard Worker if with_stats: 557*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 558*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 559*6236dae4SAndroid Build Coastguard Worker ]) 560*6236dae4SAndroid Build Coastguard Worker return self._raw(urls, alpn_proto=alpn_proto, options=extra_args, 561*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 562*6236dae4SAndroid Build Coastguard Worker with_headers=with_headers, 563*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile, 564*6236dae4SAndroid Build Coastguard Worker with_tcpdump=with_tcpdump) 565*6236dae4SAndroid Build Coastguard Worker 566*6236dae4SAndroid Build Coastguard Worker def http_upload(self, urls: List[str], data: str, 567*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 568*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 569*6236dae4SAndroid Build Coastguard Worker with_headers: bool = False, 570*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 571*6236dae4SAndroid Build Coastguard Worker with_tcpdump: bool = False, 572*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 573*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 574*6236dae4SAndroid Build Coastguard Worker extra_args = [] 575*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 576*6236dae4SAndroid Build Coastguard Worker '--data-binary', data, '-o', 'download_#1.data', 577*6236dae4SAndroid Build Coastguard Worker ]) 578*6236dae4SAndroid Build Coastguard Worker if with_stats: 579*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 580*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 581*6236dae4SAndroid Build Coastguard Worker ]) 582*6236dae4SAndroid Build Coastguard Worker return self._raw(urls, alpn_proto=alpn_proto, options=extra_args, 583*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 584*6236dae4SAndroid Build Coastguard Worker with_headers=with_headers, 585*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile, 586*6236dae4SAndroid Build Coastguard Worker with_tcpdump=with_tcpdump) 587*6236dae4SAndroid Build Coastguard Worker 588*6236dae4SAndroid Build Coastguard Worker def http_delete(self, urls: List[str], 589*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 590*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 591*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 592*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 593*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 594*6236dae4SAndroid Build Coastguard Worker extra_args = [] 595*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 596*6236dae4SAndroid Build Coastguard Worker '-X', 'DELETE', '-o', '/dev/null', 597*6236dae4SAndroid Build Coastguard Worker ]) 598*6236dae4SAndroid Build Coastguard Worker if with_stats: 599*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 600*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 601*6236dae4SAndroid Build Coastguard Worker ]) 602*6236dae4SAndroid Build Coastguard Worker return self._raw(urls, alpn_proto=alpn_proto, options=extra_args, 603*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 604*6236dae4SAndroid Build Coastguard Worker with_headers=False, 605*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile) 606*6236dae4SAndroid Build Coastguard Worker 607*6236dae4SAndroid Build Coastguard Worker def http_put(self, urls: List[str], data=None, fdata=None, 608*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 609*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 610*6236dae4SAndroid Build Coastguard Worker with_headers: bool = False, 611*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 612*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 613*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 614*6236dae4SAndroid Build Coastguard Worker extra_args = [] 615*6236dae4SAndroid Build Coastguard Worker if fdata is not None: 616*6236dae4SAndroid Build Coastguard Worker extra_args.extend(['-T', fdata]) 617*6236dae4SAndroid Build Coastguard Worker elif data is not None: 618*6236dae4SAndroid Build Coastguard Worker extra_args.extend(['-T', '-']) 619*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 620*6236dae4SAndroid Build Coastguard Worker '-o', 'download_#1.data', 621*6236dae4SAndroid Build Coastguard Worker ]) 622*6236dae4SAndroid Build Coastguard Worker if with_stats: 623*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 624*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 625*6236dae4SAndroid Build Coastguard Worker ]) 626*6236dae4SAndroid Build Coastguard Worker return self._raw(urls, intext=data, 627*6236dae4SAndroid Build Coastguard Worker alpn_proto=alpn_proto, options=extra_args, 628*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 629*6236dae4SAndroid Build Coastguard Worker with_headers=with_headers, 630*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile) 631*6236dae4SAndroid Build Coastguard Worker 632*6236dae4SAndroid Build Coastguard Worker def http_form(self, urls: List[str], form: Dict[str, str], 633*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 634*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 635*6236dae4SAndroid Build Coastguard Worker with_headers: bool = False, 636*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 637*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 638*6236dae4SAndroid Build Coastguard Worker extra_args = [] 639*6236dae4SAndroid Build Coastguard Worker for key, val in form.items(): 640*6236dae4SAndroid Build Coastguard Worker extra_args.extend(['-F', f'{key}={val}']) 641*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 642*6236dae4SAndroid Build Coastguard Worker '-o', 'download_#1.data', 643*6236dae4SAndroid Build Coastguard Worker ]) 644*6236dae4SAndroid Build Coastguard Worker if with_stats: 645*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 646*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 647*6236dae4SAndroid Build Coastguard Worker ]) 648*6236dae4SAndroid Build Coastguard Worker return self._raw(urls, alpn_proto=alpn_proto, options=extra_args, 649*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 650*6236dae4SAndroid Build Coastguard Worker with_headers=with_headers) 651*6236dae4SAndroid Build Coastguard Worker 652*6236dae4SAndroid Build Coastguard Worker def ftp_get(self, urls: List[str], 653*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 654*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 655*6236dae4SAndroid Build Coastguard Worker with_tcpdump: bool = False, 656*6236dae4SAndroid Build Coastguard Worker no_save: bool = False, 657*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 658*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 659*6236dae4SAndroid Build Coastguard Worker extra_args = [] 660*6236dae4SAndroid Build Coastguard Worker if no_save: 661*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 662*6236dae4SAndroid Build Coastguard Worker '-o', '/dev/null', 663*6236dae4SAndroid Build Coastguard Worker ]) 664*6236dae4SAndroid Build Coastguard Worker else: 665*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 666*6236dae4SAndroid Build Coastguard Worker '-o', 'download_#1.data', 667*6236dae4SAndroid Build Coastguard Worker ]) 668*6236dae4SAndroid Build Coastguard Worker # remove any existing ones 669*6236dae4SAndroid Build Coastguard Worker for i in range(100): 670*6236dae4SAndroid Build Coastguard Worker self._rmf(self.download_file(i)) 671*6236dae4SAndroid Build Coastguard Worker if with_stats: 672*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 673*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 674*6236dae4SAndroid Build Coastguard Worker ]) 675*6236dae4SAndroid Build Coastguard Worker return self._raw(urls, options=extra_args, 676*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 677*6236dae4SAndroid Build Coastguard Worker with_headers=False, 678*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile, 679*6236dae4SAndroid Build Coastguard Worker with_tcpdump=with_tcpdump) 680*6236dae4SAndroid Build Coastguard Worker 681*6236dae4SAndroid Build Coastguard Worker def ftp_ssl_get(self, urls: List[str], 682*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 683*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 684*6236dae4SAndroid Build Coastguard Worker with_tcpdump: bool = False, 685*6236dae4SAndroid Build Coastguard Worker no_save: bool = False, 686*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 687*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 688*6236dae4SAndroid Build Coastguard Worker extra_args = [] 689*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 690*6236dae4SAndroid Build Coastguard Worker '--ssl-reqd', 691*6236dae4SAndroid Build Coastguard Worker ]) 692*6236dae4SAndroid Build Coastguard Worker return self.ftp_get(urls=urls, with_stats=with_stats, 693*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile, no_save=no_save, 694*6236dae4SAndroid Build Coastguard Worker with_tcpdump=with_tcpdump, 695*6236dae4SAndroid Build Coastguard Worker extra_args=extra_args) 696*6236dae4SAndroid Build Coastguard Worker 697*6236dae4SAndroid Build Coastguard Worker def ftp_upload(self, urls: List[str], 698*6236dae4SAndroid Build Coastguard Worker fupload: Optional[Any] = None, 699*6236dae4SAndroid Build Coastguard Worker updata: Optional[str] = None, 700*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 701*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 702*6236dae4SAndroid Build Coastguard Worker with_tcpdump: bool = False, 703*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 704*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 705*6236dae4SAndroid Build Coastguard Worker extra_args = [] 706*6236dae4SAndroid Build Coastguard Worker if fupload is not None: 707*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 708*6236dae4SAndroid Build Coastguard Worker '--upload-file', fupload 709*6236dae4SAndroid Build Coastguard Worker ]) 710*6236dae4SAndroid Build Coastguard Worker elif updata is not None: 711*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 712*6236dae4SAndroid Build Coastguard Worker '--upload-file', '-' 713*6236dae4SAndroid Build Coastguard Worker ]) 714*6236dae4SAndroid Build Coastguard Worker else: 715*6236dae4SAndroid Build Coastguard Worker raise Exception('need either file or data to upload') 716*6236dae4SAndroid Build Coastguard Worker if with_stats: 717*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 718*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 719*6236dae4SAndroid Build Coastguard Worker ]) 720*6236dae4SAndroid Build Coastguard Worker return self._raw(urls, options=extra_args, 721*6236dae4SAndroid Build Coastguard Worker intext=updata, 722*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 723*6236dae4SAndroid Build Coastguard Worker with_headers=False, 724*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile, 725*6236dae4SAndroid Build Coastguard Worker with_tcpdump=with_tcpdump) 726*6236dae4SAndroid Build Coastguard Worker 727*6236dae4SAndroid Build Coastguard Worker def ftp_ssl_upload(self, urls: List[str], 728*6236dae4SAndroid Build Coastguard Worker fupload: Optional[Any] = None, 729*6236dae4SAndroid Build Coastguard Worker updata: Optional[str] = None, 730*6236dae4SAndroid Build Coastguard Worker with_stats: bool = True, 731*6236dae4SAndroid Build Coastguard Worker with_profile: bool = False, 732*6236dae4SAndroid Build Coastguard Worker with_tcpdump: bool = False, 733*6236dae4SAndroid Build Coastguard Worker extra_args: Optional[List[str]] = None): 734*6236dae4SAndroid Build Coastguard Worker if extra_args is None: 735*6236dae4SAndroid Build Coastguard Worker extra_args = [] 736*6236dae4SAndroid Build Coastguard Worker extra_args.extend([ 737*6236dae4SAndroid Build Coastguard Worker '--ssl-reqd', 738*6236dae4SAndroid Build Coastguard Worker ]) 739*6236dae4SAndroid Build Coastguard Worker return self.ftp_upload(urls=urls, fupload=fupload, updata=updata, 740*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, with_profile=with_profile, 741*6236dae4SAndroid Build Coastguard Worker with_tcpdump=with_tcpdump, 742*6236dae4SAndroid Build Coastguard Worker extra_args=extra_args) 743*6236dae4SAndroid Build Coastguard Worker 744*6236dae4SAndroid Build Coastguard Worker def response_file(self, idx: int): 745*6236dae4SAndroid Build Coastguard Worker return os.path.join(self._run_dir, f'download_{idx}.data') 746*6236dae4SAndroid Build Coastguard Worker 747*6236dae4SAndroid Build Coastguard Worker def run_direct(self, args, with_stats: bool = False, with_profile: bool = False): 748*6236dae4SAndroid Build Coastguard Worker my_args = [self._curl] 749*6236dae4SAndroid Build Coastguard Worker if with_stats: 750*6236dae4SAndroid Build Coastguard Worker my_args.extend([ 751*6236dae4SAndroid Build Coastguard Worker '-w', '%{json}\\n' 752*6236dae4SAndroid Build Coastguard Worker ]) 753*6236dae4SAndroid Build Coastguard Worker my_args.extend([ 754*6236dae4SAndroid Build Coastguard Worker '-o', 'download.data', 755*6236dae4SAndroid Build Coastguard Worker ]) 756*6236dae4SAndroid Build Coastguard Worker my_args.extend(args) 757*6236dae4SAndroid Build Coastguard Worker return self._run(args=my_args, with_stats=with_stats, with_profile=with_profile) 758*6236dae4SAndroid Build Coastguard Worker 759*6236dae4SAndroid Build Coastguard Worker def _run(self, args, intext='', with_stats: bool = False, 760*6236dae4SAndroid Build Coastguard Worker with_profile: bool = True, with_tcpdump: bool = False): 761*6236dae4SAndroid Build Coastguard Worker self._rmf(self._stdoutfile) 762*6236dae4SAndroid Build Coastguard Worker self._rmf(self._stderrfile) 763*6236dae4SAndroid Build Coastguard Worker self._rmf(self._headerfile) 764*6236dae4SAndroid Build Coastguard Worker exception = None 765*6236dae4SAndroid Build Coastguard Worker profile = None 766*6236dae4SAndroid Build Coastguard Worker tcpdump = None 767*6236dae4SAndroid Build Coastguard Worker started_at = datetime.now() 768*6236dae4SAndroid Build Coastguard Worker if with_tcpdump: 769*6236dae4SAndroid Build Coastguard Worker tcpdump = RunTcpDump(self.env, self._run_dir) 770*6236dae4SAndroid Build Coastguard Worker tcpdump.start() 771*6236dae4SAndroid Build Coastguard Worker try: 772*6236dae4SAndroid Build Coastguard Worker with open(self._stdoutfile, 'w') as cout, open(self._stderrfile, 'w') as cerr: 773*6236dae4SAndroid Build Coastguard Worker if with_profile: 774*6236dae4SAndroid Build Coastguard Worker end_at = started_at + timedelta(seconds=self._timeout) \ 775*6236dae4SAndroid Build Coastguard Worker if self._timeout else None 776*6236dae4SAndroid Build Coastguard Worker log.info(f'starting: {args}') 777*6236dae4SAndroid Build Coastguard Worker p = subprocess.Popen(args, stderr=cerr, stdout=cout, 778*6236dae4SAndroid Build Coastguard Worker cwd=self._run_dir, shell=False, 779*6236dae4SAndroid Build Coastguard Worker env=self._run_env) 780*6236dae4SAndroid Build Coastguard Worker profile = RunProfile(p.pid, started_at, self._run_dir) 781*6236dae4SAndroid Build Coastguard Worker if intext is not None and False: 782*6236dae4SAndroid Build Coastguard Worker p.communicate(input=intext.encode(), timeout=1) 783*6236dae4SAndroid Build Coastguard Worker ptimeout = 0.0 784*6236dae4SAndroid Build Coastguard Worker while True: 785*6236dae4SAndroid Build Coastguard Worker try: 786*6236dae4SAndroid Build Coastguard Worker p.wait(timeout=ptimeout) 787*6236dae4SAndroid Build Coastguard Worker break 788*6236dae4SAndroid Build Coastguard Worker except subprocess.TimeoutExpired: 789*6236dae4SAndroid Build Coastguard Worker if end_at and datetime.now() >= end_at: 790*6236dae4SAndroid Build Coastguard Worker p.kill() 791*6236dae4SAndroid Build Coastguard Worker raise subprocess.TimeoutExpired(cmd=args, timeout=self._timeout) 792*6236dae4SAndroid Build Coastguard Worker profile.sample() 793*6236dae4SAndroid Build Coastguard Worker ptimeout = 0.01 794*6236dae4SAndroid Build Coastguard Worker exitcode = p.returncode 795*6236dae4SAndroid Build Coastguard Worker profile.finish() 796*6236dae4SAndroid Build Coastguard Worker log.info(f'done: exit={exitcode}, profile={profile}') 797*6236dae4SAndroid Build Coastguard Worker else: 798*6236dae4SAndroid Build Coastguard Worker p = subprocess.run(args, stderr=cerr, stdout=cout, 799*6236dae4SAndroid Build Coastguard Worker cwd=self._run_dir, shell=False, 800*6236dae4SAndroid Build Coastguard Worker input=intext.encode() if intext else None, 801*6236dae4SAndroid Build Coastguard Worker timeout=self._timeout, 802*6236dae4SAndroid Build Coastguard Worker env=self._run_env) 803*6236dae4SAndroid Build Coastguard Worker exitcode = p.returncode 804*6236dae4SAndroid Build Coastguard Worker except subprocess.TimeoutExpired: 805*6236dae4SAndroid Build Coastguard Worker now = datetime.now() 806*6236dae4SAndroid Build Coastguard Worker duration = now - started_at 807*6236dae4SAndroid Build Coastguard Worker log.warning(f'Timeout at {now} after {duration.total_seconds()}s ' 808*6236dae4SAndroid Build Coastguard Worker f'(configured {self._timeout}s): {args}') 809*6236dae4SAndroid Build Coastguard Worker exitcode = -1 810*6236dae4SAndroid Build Coastguard Worker exception = 'TimeoutExpired' 811*6236dae4SAndroid Build Coastguard Worker if tcpdump: 812*6236dae4SAndroid Build Coastguard Worker tcpdump.finish() 813*6236dae4SAndroid Build Coastguard Worker coutput = open(self._stdoutfile).readlines() 814*6236dae4SAndroid Build Coastguard Worker cerrput = open(self._stderrfile).readlines() 815*6236dae4SAndroid Build Coastguard Worker return ExecResult(args=args, exit_code=exitcode, exception=exception, 816*6236dae4SAndroid Build Coastguard Worker stdout=coutput, stderr=cerrput, 817*6236dae4SAndroid Build Coastguard Worker duration=datetime.now() - started_at, 818*6236dae4SAndroid Build Coastguard Worker with_stats=with_stats, 819*6236dae4SAndroid Build Coastguard Worker profile=profile, tcpdump=tcpdump) 820*6236dae4SAndroid Build Coastguard Worker 821*6236dae4SAndroid Build Coastguard Worker def _raw(self, urls, intext='', timeout=None, options=None, insecure=False, 822*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 823*6236dae4SAndroid Build Coastguard Worker force_resolve=True, 824*6236dae4SAndroid Build Coastguard Worker with_stats=False, 825*6236dae4SAndroid Build Coastguard Worker with_headers=True, 826*6236dae4SAndroid Build Coastguard Worker def_tracing=True, 827*6236dae4SAndroid Build Coastguard Worker with_profile=False, 828*6236dae4SAndroid Build Coastguard Worker with_tcpdump=False): 829*6236dae4SAndroid Build Coastguard Worker args = self._complete_args( 830*6236dae4SAndroid Build Coastguard Worker urls=urls, timeout=timeout, options=options, insecure=insecure, 831*6236dae4SAndroid Build Coastguard Worker alpn_proto=alpn_proto, force_resolve=force_resolve, 832*6236dae4SAndroid Build Coastguard Worker with_headers=with_headers, def_tracing=def_tracing) 833*6236dae4SAndroid Build Coastguard Worker r = self._run(args, intext=intext, with_stats=with_stats, 834*6236dae4SAndroid Build Coastguard Worker with_profile=with_profile, with_tcpdump=with_tcpdump) 835*6236dae4SAndroid Build Coastguard Worker if r.exit_code == 0 and with_headers: 836*6236dae4SAndroid Build Coastguard Worker self._parse_headerfile(self._headerfile, r=r) 837*6236dae4SAndroid Build Coastguard Worker return r 838*6236dae4SAndroid Build Coastguard Worker 839*6236dae4SAndroid Build Coastguard Worker def _complete_args(self, urls, timeout=None, options=None, 840*6236dae4SAndroid Build Coastguard Worker insecure=False, force_resolve=True, 841*6236dae4SAndroid Build Coastguard Worker alpn_proto: Optional[str] = None, 842*6236dae4SAndroid Build Coastguard Worker with_headers: bool = True, 843*6236dae4SAndroid Build Coastguard Worker def_tracing: bool = True): 844*6236dae4SAndroid Build Coastguard Worker if not isinstance(urls, list): 845*6236dae4SAndroid Build Coastguard Worker urls = [urls] 846*6236dae4SAndroid Build Coastguard Worker 847*6236dae4SAndroid Build Coastguard Worker args = [self._curl, "-s", "--path-as-is"] 848*6236dae4SAndroid Build Coastguard Worker if 'CURL_TEST_EVENT' in os.environ: 849*6236dae4SAndroid Build Coastguard Worker args.append('--test-event') 850*6236dae4SAndroid Build Coastguard Worker 851*6236dae4SAndroid Build Coastguard Worker if with_headers: 852*6236dae4SAndroid Build Coastguard Worker args.extend(["-D", self._headerfile]) 853*6236dae4SAndroid Build Coastguard Worker if def_tracing is not False and not self._silent: 854*6236dae4SAndroid Build Coastguard Worker args.extend(['-v', '--trace-ids', '--trace-time']) 855*6236dae4SAndroid Build Coastguard Worker if self.env.verbose > 1: 856*6236dae4SAndroid Build Coastguard Worker args.extend(['--trace-config', 'http/2,http/3,h2-proxy,h1-proxy']) 857*6236dae4SAndroid Build Coastguard Worker 858*6236dae4SAndroid Build Coastguard Worker active_options = options 859*6236dae4SAndroid Build Coastguard Worker if options is not None and '--next' in options: 860*6236dae4SAndroid Build Coastguard Worker active_options = options[options.index('--next') + 1:] 861*6236dae4SAndroid Build Coastguard Worker 862*6236dae4SAndroid Build Coastguard Worker for url in urls: 863*6236dae4SAndroid Build Coastguard Worker u = urlparse(urls[0]) 864*6236dae4SAndroid Build Coastguard Worker if options: 865*6236dae4SAndroid Build Coastguard Worker args.extend(options) 866*6236dae4SAndroid Build Coastguard Worker if alpn_proto is not None: 867*6236dae4SAndroid Build Coastguard Worker if alpn_proto not in self.ALPN_ARG: 868*6236dae4SAndroid Build Coastguard Worker raise Exception(f'unknown ALPN protocol: "{alpn_proto}"') 869*6236dae4SAndroid Build Coastguard Worker args.append(self.ALPN_ARG[alpn_proto]) 870*6236dae4SAndroid Build Coastguard Worker 871*6236dae4SAndroid Build Coastguard Worker if u.scheme == 'http': 872*6236dae4SAndroid Build Coastguard Worker pass 873*6236dae4SAndroid Build Coastguard Worker elif insecure: 874*6236dae4SAndroid Build Coastguard Worker args.append('--insecure') 875*6236dae4SAndroid Build Coastguard Worker elif active_options and "--cacert" in active_options: 876*6236dae4SAndroid Build Coastguard Worker pass 877*6236dae4SAndroid Build Coastguard Worker elif u.hostname: 878*6236dae4SAndroid Build Coastguard Worker args.extend(["--cacert", self.env.ca.cert_file]) 879*6236dae4SAndroid Build Coastguard Worker 880*6236dae4SAndroid Build Coastguard Worker if force_resolve and u.hostname and u.hostname != 'localhost' \ 881*6236dae4SAndroid Build Coastguard Worker and not re.match(r'^(\d+|\[|:).*', u.hostname): 882*6236dae4SAndroid Build Coastguard Worker port = u.port if u.port else 443 883*6236dae4SAndroid Build Coastguard Worker args.extend([ 884*6236dae4SAndroid Build Coastguard Worker '--resolve', f'{u.hostname}:{port}:{self._server_addr}', 885*6236dae4SAndroid Build Coastguard Worker ]) 886*6236dae4SAndroid Build Coastguard Worker if timeout is not None and int(timeout) > 0: 887*6236dae4SAndroid Build Coastguard Worker args.extend(["--connect-timeout", str(int(timeout))]) 888*6236dae4SAndroid Build Coastguard Worker args.append(url) 889*6236dae4SAndroid Build Coastguard Worker return args 890*6236dae4SAndroid Build Coastguard Worker 891*6236dae4SAndroid Build Coastguard Worker def _parse_headerfile(self, headerfile: str, r: Optional[ExecResult] = None) -> ExecResult: 892*6236dae4SAndroid Build Coastguard Worker lines = open(headerfile).readlines() 893*6236dae4SAndroid Build Coastguard Worker if r is None: 894*6236dae4SAndroid Build Coastguard Worker r = ExecResult(args=[], exit_code=0, stdout=[], stderr=[]) 895*6236dae4SAndroid Build Coastguard Worker 896*6236dae4SAndroid Build Coastguard Worker response = None 897*6236dae4SAndroid Build Coastguard Worker 898*6236dae4SAndroid Build Coastguard Worker def fin_response(resp): 899*6236dae4SAndroid Build Coastguard Worker if resp: 900*6236dae4SAndroid Build Coastguard Worker r.add_response(resp) 901*6236dae4SAndroid Build Coastguard Worker 902*6236dae4SAndroid Build Coastguard Worker expected = ['status'] 903*6236dae4SAndroid Build Coastguard Worker for line in lines: 904*6236dae4SAndroid Build Coastguard Worker line = line.strip() 905*6236dae4SAndroid Build Coastguard Worker if re.match(r'^$', line): 906*6236dae4SAndroid Build Coastguard Worker if 'trailer' in expected: 907*6236dae4SAndroid Build Coastguard Worker # end of trailers 908*6236dae4SAndroid Build Coastguard Worker fin_response(response) 909*6236dae4SAndroid Build Coastguard Worker response = None 910*6236dae4SAndroid Build Coastguard Worker expected = ['status'] 911*6236dae4SAndroid Build Coastguard Worker elif 'header' in expected: 912*6236dae4SAndroid Build Coastguard Worker # end of header, another status or trailers might follow 913*6236dae4SAndroid Build Coastguard Worker expected = ['status', 'trailer'] 914*6236dae4SAndroid Build Coastguard Worker else: 915*6236dae4SAndroid Build Coastguard Worker assert False, f"unexpected line: '{line}'" 916*6236dae4SAndroid Build Coastguard Worker continue 917*6236dae4SAndroid Build Coastguard Worker if 'status' in expected: 918*6236dae4SAndroid Build Coastguard Worker # log.debug("reading 1st response line: %s", line) 919*6236dae4SAndroid Build Coastguard Worker m = re.match(r'^(\S+) (\d+)( .*)?$', line) 920*6236dae4SAndroid Build Coastguard Worker if m: 921*6236dae4SAndroid Build Coastguard Worker fin_response(response) 922*6236dae4SAndroid Build Coastguard Worker response = { 923*6236dae4SAndroid Build Coastguard Worker "protocol": m.group(1), 924*6236dae4SAndroid Build Coastguard Worker "status": int(m.group(2)), 925*6236dae4SAndroid Build Coastguard Worker "description": m.group(3), 926*6236dae4SAndroid Build Coastguard Worker "header": {}, 927*6236dae4SAndroid Build Coastguard Worker "trailer": {}, 928*6236dae4SAndroid Build Coastguard Worker "body": r.outraw 929*6236dae4SAndroid Build Coastguard Worker } 930*6236dae4SAndroid Build Coastguard Worker expected = ['header'] 931*6236dae4SAndroid Build Coastguard Worker continue 932*6236dae4SAndroid Build Coastguard Worker if 'trailer' in expected: 933*6236dae4SAndroid Build Coastguard Worker m = re.match(r'^([^:]+):\s*(.*)$', line) 934*6236dae4SAndroid Build Coastguard Worker if m: 935*6236dae4SAndroid Build Coastguard Worker response['trailer'][m.group(1).lower()] = m.group(2) 936*6236dae4SAndroid Build Coastguard Worker continue 937*6236dae4SAndroid Build Coastguard Worker if 'header' in expected: 938*6236dae4SAndroid Build Coastguard Worker m = re.match(r'^([^:]+):\s*(.*)$', line) 939*6236dae4SAndroid Build Coastguard Worker if m: 940*6236dae4SAndroid Build Coastguard Worker response['header'][m.group(1).lower()] = m.group(2) 941*6236dae4SAndroid Build Coastguard Worker continue 942*6236dae4SAndroid Build Coastguard Worker assert False, f"unexpected line: '{line}, expected: {expected}'" 943*6236dae4SAndroid Build Coastguard Worker 944*6236dae4SAndroid Build Coastguard Worker fin_response(response) 945*6236dae4SAndroid Build Coastguard Worker return r 946