1*134e1779SJakub Wojciech Klama#! /usr/bin/env python 2*134e1779SJakub Wojciech Klama 3*134e1779SJakub Wojciech Klama""" 4*134e1779SJakub Wojciech KlamaRun various tests, as a client. 5*134e1779SJakub Wojciech Klama""" 6*134e1779SJakub Wojciech Klama 7*134e1779SJakub Wojciech Klamafrom __future__ import print_function 8*134e1779SJakub Wojciech Klama 9*134e1779SJakub Wojciech Klamaimport argparse 10*134e1779SJakub Wojciech Klamatry: 11*134e1779SJakub Wojciech Klama import ConfigParser as configparser 12*134e1779SJakub Wojciech Klamaexcept ImportError: 13*134e1779SJakub Wojciech Klama import configparser 14*134e1779SJakub Wojciech Klamaimport functools 15*134e1779SJakub Wojciech Klamaimport logging 16*134e1779SJakub Wojciech Klamaimport os 17*134e1779SJakub Wojciech Klamaimport socket 18*134e1779SJakub Wojciech Klamaimport struct 19*134e1779SJakub Wojciech Klamaimport sys 20*134e1779SJakub Wojciech Klamaimport time 21*134e1779SJakub Wojciech Klamaimport traceback 22*134e1779SJakub Wojciech Klama 23*134e1779SJakub Wojciech Klamaimport p9conn 24*134e1779SJakub Wojciech Klamaimport protocol 25*134e1779SJakub Wojciech Klama 26*134e1779SJakub Wojciech KlamaLocalError = p9conn.LocalError 27*134e1779SJakub Wojciech KlamaRemoteError = p9conn.RemoteError 28*134e1779SJakub Wojciech KlamaTEError = p9conn.TEError 29*134e1779SJakub Wojciech Klama 30*134e1779SJakub Wojciech Klamaclass TestState(object): 31*134e1779SJakub Wojciech Klama def __init__(self): 32*134e1779SJakub Wojciech Klama self.config = None 33*134e1779SJakub Wojciech Klama self.logger = None 34*134e1779SJakub Wojciech Klama self.successes = 0 35*134e1779SJakub Wojciech Klama self.skips = 0 36*134e1779SJakub Wojciech Klama self.failures = 0 37*134e1779SJakub Wojciech Klama self.exceptions = 0 38*134e1779SJakub Wojciech Klama self.clnt_tab = {} 39*134e1779SJakub Wojciech Klama self.mkclient = None 40*134e1779SJakub Wojciech Klama self.stop = False 41*134e1779SJakub Wojciech Klama self.gid = 0 42*134e1779SJakub Wojciech Klama 43*134e1779SJakub Wojciech Klama def ccc(self, cid=None): 44*134e1779SJakub Wojciech Klama """ 45*134e1779SJakub Wojciech Klama Connect or reconnect as client (ccc = check and connect client). 46*134e1779SJakub Wojciech Klama 47*134e1779SJakub Wojciech Klama If caller provides a cid (client ID) we check that specific 48*134e1779SJakub Wojciech Klama client. Otherwise the default ID ('base') is used. 49*134e1779SJakub Wojciech Klama In any case we return the now-connected client, plus the 50*134e1779SJakub Wojciech Klama attachment (session info) if any. 51*134e1779SJakub Wojciech Klama """ 52*134e1779SJakub Wojciech Klama if cid is None: 53*134e1779SJakub Wojciech Klama cid = 'base' 54*134e1779SJakub Wojciech Klama pair = self.clnt_tab.get(cid) 55*134e1779SJakub Wojciech Klama if pair is None: 56*134e1779SJakub Wojciech Klama clnt = self.mkclient() 57*134e1779SJakub Wojciech Klama pair = [clnt, None] 58*134e1779SJakub Wojciech Klama self.clnt_tab[cid] = pair 59*134e1779SJakub Wojciech Klama else: 60*134e1779SJakub Wojciech Klama clnt = pair[0] 61*134e1779SJakub Wojciech Klama if not clnt.is_connected(): 62*134e1779SJakub Wojciech Klama clnt.connect() 63*134e1779SJakub Wojciech Klama return pair 64*134e1779SJakub Wojciech Klama 65*134e1779SJakub Wojciech Klama def dcc(self, cid=None): 66*134e1779SJakub Wojciech Klama """ 67*134e1779SJakub Wojciech Klama Disconnect client (disconnect checked client). If no specific 68*134e1779SJakub Wojciech Klama client ID is provided, this disconnects ALL checked clients! 69*134e1779SJakub Wojciech Klama """ 70*134e1779SJakub Wojciech Klama if cid is None: 71*134e1779SJakub Wojciech Klama for cid in list(self.clnt_tab.keys()): 72*134e1779SJakub Wojciech Klama self.dcc(cid) 73*134e1779SJakub Wojciech Klama pair = self.clnt_tab.get(cid) 74*134e1779SJakub Wojciech Klama if pair is not None: 75*134e1779SJakub Wojciech Klama clnt = pair[0] 76*134e1779SJakub Wojciech Klama if clnt.is_connected(): 77*134e1779SJakub Wojciech Klama clnt.shutdown() 78*134e1779SJakub Wojciech Klama del self.clnt_tab[cid] 79*134e1779SJakub Wojciech Klama 80*134e1779SJakub Wojciech Klama def ccs(self, cid=None): 81*134e1779SJakub Wojciech Klama """ 82*134e1779SJakub Wojciech Klama Like ccc, but establish a session as well, by setting up 83*134e1779SJakub Wojciech Klama the uname/n_uname. 84*134e1779SJakub Wojciech Klama 85*134e1779SJakub Wojciech Klama Return the client instance (only). 86*134e1779SJakub Wojciech Klama """ 87*134e1779SJakub Wojciech Klama pair = self.ccc(cid) 88*134e1779SJakub Wojciech Klama clnt = pair[0] 89*134e1779SJakub Wojciech Klama if pair[1] is None: 90*134e1779SJakub Wojciech Klama # No session yet - establish one. Note, this may fail. 91*134e1779SJakub Wojciech Klama section = None if cid is None else ('client-' + cid) 92*134e1779SJakub Wojciech Klama aname = getconf(self.config, section, 'aname', '') 93*134e1779SJakub Wojciech Klama uname = getconf(self.config, section, 'uname', '') 94*134e1779SJakub Wojciech Klama if clnt.proto > protocol.plain: 95*134e1779SJakub Wojciech Klama n_uname = getint(self.config, section, 'n_uname', 1001) 96*134e1779SJakub Wojciech Klama else: 97*134e1779SJakub Wojciech Klama n_uname = None 98*134e1779SJakub Wojciech Klama clnt.attach(afid=None, aname=aname, uname=uname, n_uname=n_uname) 99*134e1779SJakub Wojciech Klama pair[1] = (aname, uname, n_uname) 100*134e1779SJakub Wojciech Klama return clnt 101*134e1779SJakub Wojciech Klama 102*134e1779SJakub Wojciech Klamadef getconf(conf, section, name, default=None, rtype=str): 103*134e1779SJakub Wojciech Klama """ 104*134e1779SJakub Wojciech Klama Get configuration item for given section, or for "client" if 105*134e1779SJakub Wojciech Klama there is no entry for that particular section (or if section 106*134e1779SJakub Wojciech Klama is None). 107*134e1779SJakub Wojciech Klama 108*134e1779SJakub Wojciech Klama This lets us get specific values for specific tests or 109*134e1779SJakub Wojciech Klama groups ([foo] name=value), falling back to general values 110*134e1779SJakub Wojciech Klama ([client] name=value). 111*134e1779SJakub Wojciech Klama 112*134e1779SJakub Wojciech Klama The type of the returned value <rtype> can be str, int, bool, 113*134e1779SJakub Wojciech Klama or float. The default is str (and see getconfint, getconfbool, 114*134e1779SJakub Wojciech Klama getconffloat below). 115*134e1779SJakub Wojciech Klama 116*134e1779SJakub Wojciech Klama A default value may be supplied; if it is, that's the default 117*134e1779SJakub Wojciech Klama return value (this default should have the right type). If 118*134e1779SJakub Wojciech Klama no default is supplied, a missing value is an error. 119*134e1779SJakub Wojciech Klama """ 120*134e1779SJakub Wojciech Klama try: 121*134e1779SJakub Wojciech Klama # note: conf.get(None, 'foo') raises NoSectionError 122*134e1779SJakub Wojciech Klama where = section 123*134e1779SJakub Wojciech Klama result = conf.get(where, name) 124*134e1779SJakub Wojciech Klama except (configparser.NoSectionError, configparser.NoOptionError): 125*134e1779SJakub Wojciech Klama try: 126*134e1779SJakub Wojciech Klama where = 'client' 127*134e1779SJakub Wojciech Klama result = conf.get(where, name) 128*134e1779SJakub Wojciech Klama except configparser.NoSectionError: 129*134e1779SJakub Wojciech Klama sys.exit('no [{0}] section in configuration!'.format(where)) 130*134e1779SJakub Wojciech Klama except configparser.NoOptionError: 131*134e1779SJakub Wojciech Klama if default is not None: 132*134e1779SJakub Wojciech Klama return default 133*134e1779SJakub Wojciech Klama if section is not None: 134*134e1779SJakub Wojciech Klama where = '[{0}] or [{1}]'.format(section, where) 135*134e1779SJakub Wojciech Klama else: 136*134e1779SJakub Wojciech Klama where = '[{0}]'.format(where) 137*134e1779SJakub Wojciech Klama raise LocalError('need {0}=value in {1}'.format(name, where)) 138*134e1779SJakub Wojciech Klama where = '[{0}]'.format(where) 139*134e1779SJakub Wojciech Klama if rtype is str: 140*134e1779SJakub Wojciech Klama return result 141*134e1779SJakub Wojciech Klama if rtype is int: 142*134e1779SJakub Wojciech Klama return int(result) 143*134e1779SJakub Wojciech Klama if rtype is float: 144*134e1779SJakub Wojciech Klama return float(result) 145*134e1779SJakub Wojciech Klama if rtype is bool: 146*134e1779SJakub Wojciech Klama if result.lower() in ('1', 't', 'true', 'y', 'yes'): 147*134e1779SJakub Wojciech Klama return True 148*134e1779SJakub Wojciech Klama if result.lower() in ('0', 'f', 'false', 'n', 'no'): 149*134e1779SJakub Wojciech Klama return False 150*134e1779SJakub Wojciech Klama raise ValueError('{0} {1}={2}: invalid boolean'.format(where, name, 151*134e1779SJakub Wojciech Klama result)) 152*134e1779SJakub Wojciech Klama raise ValueError('{0} {1}={2}: internal error: bad result type ' 153*134e1779SJakub Wojciech Klama '{3!r}'.format(where, name, result, rtype)) 154*134e1779SJakub Wojciech Klama 155*134e1779SJakub Wojciech Klamadef getint(conf, section, name, default=None): 156*134e1779SJakub Wojciech Klama "get integer config item" 157*134e1779SJakub Wojciech Klama return getconf(conf, section, name, default, int) 158*134e1779SJakub Wojciech Klama 159*134e1779SJakub Wojciech Klamadef getfloat(conf, section, name, default=None): 160*134e1779SJakub Wojciech Klama "get float config item" 161*134e1779SJakub Wojciech Klama return getconf(conf, section, name, default, float) 162*134e1779SJakub Wojciech Klama 163*134e1779SJakub Wojciech Klamadef getbool(conf, section, name, default=None): 164*134e1779SJakub Wojciech Klama "get boolean config item" 165*134e1779SJakub Wojciech Klama return getconf(conf, section, name, default, bool) 166*134e1779SJakub Wojciech Klama 167*134e1779SJakub Wojciech Klamadef pluralize(n, singular, plural): 168*134e1779SJakub Wojciech Klama "return singular or plural based on value of n" 169*134e1779SJakub Wojciech Klama return plural if n != 1 else singular 170*134e1779SJakub Wojciech Klama 171*134e1779SJakub Wojciech Klamaclass TCDone(Exception): 172*134e1779SJakub Wojciech Klama "used in succ/fail/skip - skips rest of testcase with" 173*134e1779SJakub Wojciech Klama pass 174*134e1779SJakub Wojciech Klama 175*134e1779SJakub Wojciech Klamaclass TestCase(object): 176*134e1779SJakub Wojciech Klama """ 177*134e1779SJakub Wojciech Klama Start a test case. Most callers must then do a ccs() to connect. 178*134e1779SJakub Wojciech Klama 179*134e1779SJakub Wojciech Klama A failed test will generally disconnect from the server; a 180*134e1779SJakub Wojciech Klama new ccs() will reconnect, if the server is still alive. 181*134e1779SJakub Wojciech Klama """ 182*134e1779SJakub Wojciech Klama def __init__(self, name, tstate): 183*134e1779SJakub Wojciech Klama self.name = name 184*134e1779SJakub Wojciech Klama self.status = None 185*134e1779SJakub Wojciech Klama self.detail = None 186*134e1779SJakub Wojciech Klama self.tstate = tstate 187*134e1779SJakub Wojciech Klama self._shutdown = None 188*134e1779SJakub Wojciech Klama self._autoclunk = None 189*134e1779SJakub Wojciech Klama self._acconn = None 190*134e1779SJakub Wojciech Klama 191*134e1779SJakub Wojciech Klama def auto_disconnect(self, conn): 192*134e1779SJakub Wojciech Klama self._shutdown = conn 193*134e1779SJakub Wojciech Klama 194*134e1779SJakub Wojciech Klama def succ(self, detail=None): 195*134e1779SJakub Wojciech Klama "set success status" 196*134e1779SJakub Wojciech Klama self.status = 'SUCC' 197*134e1779SJakub Wojciech Klama self.detail = detail 198*134e1779SJakub Wojciech Klama raise TCDone() 199*134e1779SJakub Wojciech Klama 200*134e1779SJakub Wojciech Klama def fail(self, detail): 201*134e1779SJakub Wojciech Klama "set failure status" 202*134e1779SJakub Wojciech Klama self.status = 'FAIL' 203*134e1779SJakub Wojciech Klama self.detail = detail 204*134e1779SJakub Wojciech Klama raise TCDone() 205*134e1779SJakub Wojciech Klama 206*134e1779SJakub Wojciech Klama def skip(self, detail=None): 207*134e1779SJakub Wojciech Klama "set skip status" 208*134e1779SJakub Wojciech Klama self.status = 'SKIP' 209*134e1779SJakub Wojciech Klama self.detail = detail 210*134e1779SJakub Wojciech Klama raise TCDone() 211*134e1779SJakub Wojciech Klama 212*134e1779SJakub Wojciech Klama def autoclunk(self, fid): 213*134e1779SJakub Wojciech Klama "mark fid to be closed/clunked on test exit" 214*134e1779SJakub Wojciech Klama if self._acconn is None: 215*134e1779SJakub Wojciech Klama raise ValueError('autoclunk: no _acconn') 216*134e1779SJakub Wojciech Klama self._autoclunk.append(fid) 217*134e1779SJakub Wojciech Klama 218*134e1779SJakub Wojciech Klama def trace(self, msg, *args, **kwargs): 219*134e1779SJakub Wojciech Klama "add tracing info to log-file output" 220*134e1779SJakub Wojciech Klama level = kwargs.pop('level', logging.INFO) 221*134e1779SJakub Wojciech Klama self.tstate.logger.log(level, ' ' + msg, *args, **kwargs) 222*134e1779SJakub Wojciech Klama 223*134e1779SJakub Wojciech Klama def ccs(self): 224*134e1779SJakub Wojciech Klama "call tstate ccs, turn socket.error connect failure into test fail" 225*134e1779SJakub Wojciech Klama try: 226*134e1779SJakub Wojciech Klama self.detail = 'connecting' 227*134e1779SJakub Wojciech Klama ret = self.tstate.ccs() 228*134e1779SJakub Wojciech Klama self.detail = None 229*134e1779SJakub Wojciech Klama self._acconn = ret 230*134e1779SJakub Wojciech Klama return ret 231*134e1779SJakub Wojciech Klama except socket.error as err: 232*134e1779SJakub Wojciech Klama self.fail(str(err)) 233*134e1779SJakub Wojciech Klama 234*134e1779SJakub Wojciech Klama def __enter__(self): 235*134e1779SJakub Wojciech Klama self.tstate.logger.log(logging.DEBUG, 'ENTER: %s', self.name) 236*134e1779SJakub Wojciech Klama self._autoclunk = [] 237*134e1779SJakub Wojciech Klama return self 238*134e1779SJakub Wojciech Klama 239*134e1779SJakub Wojciech Klama def __exit__(self, exc_type, exc_val, exc_tb): 240*134e1779SJakub Wojciech Klama tstate = self.tstate 241*134e1779SJakub Wojciech Klama eat_exc = False 242*134e1779SJakub Wojciech Klama tb_detail = None 243*134e1779SJakub Wojciech Klama if exc_type is TCDone: 244*134e1779SJakub Wojciech Klama # we exited with succ, fail, or skip 245*134e1779SJakub Wojciech Klama eat_exc = True 246*134e1779SJakub Wojciech Klama exc_type = None 247*134e1779SJakub Wojciech Klama if exc_type is not None: 248*134e1779SJakub Wojciech Klama if self.status is None: 249*134e1779SJakub Wojciech Klama self.status = 'EXCP' 250*134e1779SJakub Wojciech Klama else: 251*134e1779SJakub Wojciech Klama self.status += ' EXC' 252*134e1779SJakub Wojciech Klama if exc_type == TEError: 253*134e1779SJakub Wojciech Klama # timeout/eof - best guess is that we crashed the server! 254*134e1779SJakub Wojciech Klama eat_exc = True 255*134e1779SJakub Wojciech Klama tb_detail = ['timeout or EOF'] 256*134e1779SJakub Wojciech Klama elif exc_type in (socket.error, RemoteError, LocalError): 257*134e1779SJakub Wojciech Klama eat_exc = True 258*134e1779SJakub Wojciech Klama tb_detail = traceback.format_exception(exc_type, exc_val, 259*134e1779SJakub Wojciech Klama exc_tb) 260*134e1779SJakub Wojciech Klama level = logging.ERROR 261*134e1779SJakub Wojciech Klama tstate.failures += 1 262*134e1779SJakub Wojciech Klama tstate.exceptions += 1 263*134e1779SJakub Wojciech Klama else: 264*134e1779SJakub Wojciech Klama if self.status is None: 265*134e1779SJakub Wojciech Klama self.status = 'SUCC' 266*134e1779SJakub Wojciech Klama if self.status == 'SUCC': 267*134e1779SJakub Wojciech Klama level = logging.INFO 268*134e1779SJakub Wojciech Klama tstate.successes += 1 269*134e1779SJakub Wojciech Klama elif self.status == 'SKIP': 270*134e1779SJakub Wojciech Klama level = logging.INFO 271*134e1779SJakub Wojciech Klama tstate.skips += 1 272*134e1779SJakub Wojciech Klama else: 273*134e1779SJakub Wojciech Klama level = logging.ERROR 274*134e1779SJakub Wojciech Klama tstate.failures += 1 275*134e1779SJakub Wojciech Klama tstate.logger.log(level, '%s: %s', self.status, self.name) 276*134e1779SJakub Wojciech Klama if self.detail: 277*134e1779SJakub Wojciech Klama tstate.logger.log(level, ' detail: %s', self.detail) 278*134e1779SJakub Wojciech Klama if tb_detail: 279*134e1779SJakub Wojciech Klama for line in tb_detail: 280*134e1779SJakub Wojciech Klama tstate.logger.log(level, ' %s', line.rstrip()) 281*134e1779SJakub Wojciech Klama for fid in self._autoclunk: 282*134e1779SJakub Wojciech Klama self._acconn.clunk(fid, ignore_error=True) 283*134e1779SJakub Wojciech Klama if self._shutdown: 284*134e1779SJakub Wojciech Klama self._shutdown.shutdown() 285*134e1779SJakub Wojciech Klama return eat_exc 286*134e1779SJakub Wojciech Klama 287*134e1779SJakub Wojciech Klamadef main(): 288*134e1779SJakub Wojciech Klama "the usual main" 289*134e1779SJakub Wojciech Klama parser = argparse.ArgumentParser(description='run tests against a server') 290*134e1779SJakub Wojciech Klama 291*134e1779SJakub Wojciech Klama parser.add_argument('-c', '--config', 292*134e1779SJakub Wojciech Klama action='append', 293*134e1779SJakub Wojciech Klama help='specify additional file(s) to read (beyond testconf.ini)') 294*134e1779SJakub Wojciech Klama 295*134e1779SJakub Wojciech Klama args = parser.parse_args() 296*134e1779SJakub Wojciech Klama config = configparser.SafeConfigParser() 297*134e1779SJakub Wojciech Klama # use case sensitive keys 298*134e1779SJakub Wojciech Klama config.optionxform = str 299*134e1779SJakub Wojciech Klama 300*134e1779SJakub Wojciech Klama try: 301*134e1779SJakub Wojciech Klama with open('testconf.ini', 'r') as stream: 302*134e1779SJakub Wojciech Klama config.readfp(stream) 303*134e1779SJakub Wojciech Klama except (OSError, IOError) as err: 304*134e1779SJakub Wojciech Klama sys.exit(str(err)) 305*134e1779SJakub Wojciech Klama if args.config: 306*134e1779SJakub Wojciech Klama ok = config.read(args.config) 307*134e1779SJakub Wojciech Klama failed = set(ok) - set(args.config) 308*134e1779SJakub Wojciech Klama if len(failed): 309*134e1779SJakub Wojciech Klama nfailed = len(failed) 310*134e1779SJakub Wojciech Klama word = 'files' if nfailed > 1 else 'file' 311*134e1779SJakub Wojciech Klama failed = ', '.join(failed) 312*134e1779SJakub Wojciech Klama print('failed to read {0} {1}: {2}'.format(nfailed, word, failed)) 313*134e1779SJakub Wojciech Klama sys.exit(1) 314*134e1779SJakub Wojciech Klama 315*134e1779SJakub Wojciech Klama logging.basicConfig(level=config.get('client', 'loglevel').upper()) 316*134e1779SJakub Wojciech Klama logger = logging.getLogger(__name__) 317*134e1779SJakub Wojciech Klama tstate = TestState() 318*134e1779SJakub Wojciech Klama tstate.logger = logger 319*134e1779SJakub Wojciech Klama tstate.config = config 320*134e1779SJakub Wojciech Klama 321*134e1779SJakub Wojciech Klama server = config.get('client', 'server') 322*134e1779SJakub Wojciech Klama port = config.getint('client', 'port') 323*134e1779SJakub Wojciech Klama proto = config.get('client', 'protocol') 324*134e1779SJakub Wojciech Klama may_downgrade = config.getboolean('client', 'may_downgrade') 325*134e1779SJakub Wojciech Klama timeout = config.getfloat('client', 'timeout') 326*134e1779SJakub Wojciech Klama 327*134e1779SJakub Wojciech Klama tstate.stop = True # unless overwritten below 328*134e1779SJakub Wojciech Klama with TestCase('send bad packet', tstate) as tc: 329*134e1779SJakub Wojciech Klama tc.detail = 'connecting to {0}:{1}'.format(server, port) 330*134e1779SJakub Wojciech Klama try: 331*134e1779SJakub Wojciech Klama conn = p9conn.P9SockIO(logger, server=server, port=port) 332*134e1779SJakub Wojciech Klama except socket.error as err: 333*134e1779SJakub Wojciech Klama tc.fail('cannot connect at all (server down?)') 334*134e1779SJakub Wojciech Klama tc.auto_disconnect(conn) 335*134e1779SJakub Wojciech Klama tc.detail = None 336*134e1779SJakub Wojciech Klama pkt = struct.pack('<I', 256); 337*134e1779SJakub Wojciech Klama conn.write(pkt) 338*134e1779SJakub Wojciech Klama # ignore reply if any, we're just trying to trip the server 339*134e1779SJakub Wojciech Klama tstate.stop = False 340*134e1779SJakub Wojciech Klama tc.succ() 341*134e1779SJakub Wojciech Klama 342*134e1779SJakub Wojciech Klama if not tstate.stop: 343*134e1779SJakub Wojciech Klama tstate.mkclient = functools.partial(p9conn.P9Client, logger, 344*134e1779SJakub Wojciech Klama timeout, proto, may_downgrade, 345*134e1779SJakub Wojciech Klama server=server, port=port) 346*134e1779SJakub Wojciech Klama tstate.stop = True 347*134e1779SJakub Wojciech Klama with TestCase('send bad Tversion', tstate) as tc: 348*134e1779SJakub Wojciech Klama try: 349*134e1779SJakub Wojciech Klama clnt = tstate.mkclient() 350*134e1779SJakub Wojciech Klama except socket.error as err: 351*134e1779SJakub Wojciech Klama tc.fail('can no longer connect, did bad pkt crash server?') 352*134e1779SJakub Wojciech Klama tc.auto_disconnect(clnt) 353*134e1779SJakub Wojciech Klama clnt.set_monkey('version', b'wrongo, fishbreath!') 354*134e1779SJakub Wojciech Klama tc.detail = 'connecting' 355*134e1779SJakub Wojciech Klama try: 356*134e1779SJakub Wojciech Klama clnt.connect() 357*134e1779SJakub Wojciech Klama except RemoteError as err: 358*134e1779SJakub Wojciech Klama tstate.stop = False 359*134e1779SJakub Wojciech Klama tc.succ(err.args[0]) 360*134e1779SJakub Wojciech Klama tc.fail('server accepted a bad Tversion') 361*134e1779SJakub Wojciech Klama 362*134e1779SJakub Wojciech Klama if not tstate.stop: 363*134e1779SJakub Wojciech Klama # All NUL characters in strings are invalid. 364*134e1779SJakub Wojciech Klama with TestCase('send illegal NUL in Tversion', tstate) as tc: 365*134e1779SJakub Wojciech Klama clnt = tstate.mkclient() 366*134e1779SJakub Wojciech Klama tc.auto_disconnect(clnt) 367*134e1779SJakub Wojciech Klama clnt.set_monkey('version', b'9P2000\0') 368*134e1779SJakub Wojciech Klama # Forcibly allow downgrade so that Tversion 369*134e1779SJakub Wojciech Klama # succeeds if they ignore the \0. 370*134e1779SJakub Wojciech Klama clnt.may_downgrade = True 371*134e1779SJakub Wojciech Klama tc.detail = 'connecting' 372*134e1779SJakub Wojciech Klama try: 373*134e1779SJakub Wojciech Klama clnt.connect() 374*134e1779SJakub Wojciech Klama except (TEError, RemoteError) as err: 375*134e1779SJakub Wojciech Klama tc.succ(err.args[0]) 376*134e1779SJakub Wojciech Klama tc.fail('server accepted NUL in Tversion') 377*134e1779SJakub Wojciech Klama 378*134e1779SJakub Wojciech Klama if not tstate.stop: 379*134e1779SJakub Wojciech Klama with TestCase('connect normally', tstate) as tc: 380*134e1779SJakub Wojciech Klama tc.detail = 'connecting' 381*134e1779SJakub Wojciech Klama try: 382*134e1779SJakub Wojciech Klama tstate.ccc() 383*134e1779SJakub Wojciech Klama except RemoteError as err: 384*134e1779SJakub Wojciech Klama # can't test any further, but this might be success 385*134e1779SJakub Wojciech Klama tstate.stop = True 386*134e1779SJakub Wojciech Klama if 'they only support version' in err.args[0]: 387*134e1779SJakub Wojciech Klama tc.succ(err.args[0]) 388*134e1779SJakub Wojciech Klama tc.fail(err.args[0]) 389*134e1779SJakub Wojciech Klama tc.succ() 390*134e1779SJakub Wojciech Klama 391*134e1779SJakub Wojciech Klama if not tstate.stop: 392*134e1779SJakub Wojciech Klama with TestCase('attach with bad afid', tstate) as tc: 393*134e1779SJakub Wojciech Klama clnt = tstate.ccc()[0] 394*134e1779SJakub Wojciech Klama section = 'attach-with-bad-afid' 395*134e1779SJakub Wojciech Klama aname = getconf(tstate.config, section, 'aname', '') 396*134e1779SJakub Wojciech Klama uname = getconf(tstate.config, section, 'uname', '') 397*134e1779SJakub Wojciech Klama if clnt.proto > protocol.plain: 398*134e1779SJakub Wojciech Klama n_uname = getint(tstate.config, section, 'n_uname', 1001) 399*134e1779SJakub Wojciech Klama else: 400*134e1779SJakub Wojciech Klama n_uname = None 401*134e1779SJakub Wojciech Klama try: 402*134e1779SJakub Wojciech Klama clnt.attach(afid=42, aname=aname, uname=uname, n_uname=n_uname) 403*134e1779SJakub Wojciech Klama except RemoteError as err: 404*134e1779SJakub Wojciech Klama tc.succ(err.args[0]) 405*134e1779SJakub Wojciech Klama tc.dcc() 406*134e1779SJakub Wojciech Klama tc.fail('bad attach afid not rejected') 407*134e1779SJakub Wojciech Klama 408*134e1779SJakub Wojciech Klama try: 409*134e1779SJakub Wojciech Klama if not tstate.stop: 410*134e1779SJakub Wojciech Klama # Various Linux tests need gids. Just get them for everyone. 411*134e1779SJakub Wojciech Klama tstate.gid = getint(tstate.config, 'client', 'gid', 0) 412*134e1779SJakub Wojciech Klama more_test_cases(tstate) 413*134e1779SJakub Wojciech Klama finally: 414*134e1779SJakub Wojciech Klama tstate.dcc() 415*134e1779SJakub Wojciech Klama 416*134e1779SJakub Wojciech Klama n_tests = tstate.successes + tstate.failures 417*134e1779SJakub Wojciech Klama print('summary:') 418*134e1779SJakub Wojciech Klama if tstate.successes: 419*134e1779SJakub Wojciech Klama print('{0}/{1} tests succeeded'.format(tstate.successes, n_tests)) 420*134e1779SJakub Wojciech Klama if tstate.failures: 421*134e1779SJakub Wojciech Klama print('{0}/{1} tests failed'.format(tstate.failures, n_tests)) 422*134e1779SJakub Wojciech Klama if tstate.skips: 423*134e1779SJakub Wojciech Klama print('{0} {1} skipped'.format(tstate.skips, 424*134e1779SJakub Wojciech Klama pluralize(tstate.skips, 425*134e1779SJakub Wojciech Klama 'test', 'tests'))) 426*134e1779SJakub Wojciech Klama if tstate.exceptions: 427*134e1779SJakub Wojciech Klama print('{0} {1} occurred'.format(tstate.exceptions, 428*134e1779SJakub Wojciech Klama pluralize(tstate.exceptions, 429*134e1779SJakub Wojciech Klama 'exception', 'exceptions'))) 430*134e1779SJakub Wojciech Klama if tstate.stop: 431*134e1779SJakub Wojciech Klama print('tests stopped early') 432*134e1779SJakub Wojciech Klama return 1 if tstate.stop or tstate.exceptions or tstate.failures else 0 433*134e1779SJakub Wojciech Klama 434*134e1779SJakub Wojciech Klamadef more_test_cases(tstate): 435*134e1779SJakub Wojciech Klama "run cases that can only proceed if connecting works at all" 436*134e1779SJakub Wojciech Klama with TestCase('attach normally', tstate) as tc: 437*134e1779SJakub Wojciech Klama tc.ccs() 438*134e1779SJakub Wojciech Klama tc.succ() 439*134e1779SJakub Wojciech Klama if tstate.stop: 440*134e1779SJakub Wojciech Klama return 441*134e1779SJakub Wojciech Klama 442*134e1779SJakub Wojciech Klama # Empty string is not technically illegal. It's not clear 443*134e1779SJakub Wojciech Klama # whether it should be accepted or rejected. However, it 444*134e1779SJakub Wojciech Klama # used to crash the server entirely, so it's a desirable 445*134e1779SJakub Wojciech Klama # test case. 446*134e1779SJakub Wojciech Klama with TestCase('empty string in Twalk request', tstate) as tc: 447*134e1779SJakub Wojciech Klama clnt = tc.ccs() 448*134e1779SJakub Wojciech Klama try: 449*134e1779SJakub Wojciech Klama fid, qid = clnt.lookup(clnt.rootfid, [b'']) 450*134e1779SJakub Wojciech Klama except RemoteError as err: 451*134e1779SJakub Wojciech Klama tc.succ(err.args[0]) 452*134e1779SJakub Wojciech Klama clnt.clunk(fid) 453*134e1779SJakub Wojciech Klama tc.succ('note: empty Twalk component name not rejected') 454*134e1779SJakub Wojciech Klama 455*134e1779SJakub Wojciech Klama # Name components may not contain / 456*134e1779SJakub Wojciech Klama with TestCase('embedded / in lookup component name', tstate) as tc: 457*134e1779SJakub Wojciech Klama clnt = tc.ccs() 458*134e1779SJakub Wojciech Klama try: 459*134e1779SJakub Wojciech Klama fid, qid = clnt.lookup(clnt.rootfid, [b'/']) 460*134e1779SJakub Wojciech Klama tc.autoclunk(fid) 461*134e1779SJakub Wojciech Klama except RemoteError as err: 462*134e1779SJakub Wojciech Klama tc.succ(err.args[0]) 463*134e1779SJakub Wojciech Klama tc.fail('/ in lookup component name not rejected') 464*134e1779SJakub Wojciech Klama 465*134e1779SJakub Wojciech Klama # Proceed from a clean tree. As a side effect, this also tests 466*134e1779SJakub Wojciech Klama # either the old style readdir (read() on a directory fid) or 467*134e1779SJakub Wojciech Klama # the dot-L readdir(). 468*134e1779SJakub Wojciech Klama # 469*134e1779SJakub Wojciech Klama # The test case will fail if we don't have permission to remove 470*134e1779SJakub Wojciech Klama # some file(s). 471*134e1779SJakub Wojciech Klama with TestCase('clean up tree (readdir+remove)', tstate) as tc: 472*134e1779SJakub Wojciech Klama clnt = tc.ccs() 473*134e1779SJakub Wojciech Klama fset = clnt.uxreaddir(b'/') 474*134e1779SJakub Wojciech Klama fset = [i for i in fset if i != '.' and i != '..'] 475*134e1779SJakub Wojciech Klama tc.trace("what's there initially: {0!r}".format(fset)) 476*134e1779SJakub Wojciech Klama try: 477*134e1779SJakub Wojciech Klama clnt.uxremove(b'/', force=False, recurse=True) 478*134e1779SJakub Wojciech Klama except RemoteError as err: 479*134e1779SJakub Wojciech Klama tc.trace('failed to read or clean up tree', level=logging.ERROR) 480*134e1779SJakub Wojciech Klama tc.trace('this might be a permissions error', level=logging.ERROR) 481*134e1779SJakub Wojciech Klama tstate.stop = True 482*134e1779SJakub Wojciech Klama tc.fail(str(err)) 483*134e1779SJakub Wojciech Klama fset = clnt.uxreaddir(b'/') 484*134e1779SJakub Wojciech Klama fset = [i for i in fset if i != '.' and i != '..'] 485*134e1779SJakub Wojciech Klama tc.trace("what's left after removing everything: {0!r}".format(fset)) 486*134e1779SJakub Wojciech Klama if fset: 487*134e1779SJakub Wojciech Klama tstate.stop = True 488*134e1779SJakub Wojciech Klama tc.trace('note: could be a permissions error', level=logging.ERROR) 489*134e1779SJakub Wojciech Klama tc.fail('/ not empty after removing all: {0!r}'.format(fset)) 490*134e1779SJakub Wojciech Klama tc.succ() 491*134e1779SJakub Wojciech Klama if tstate.stop: 492*134e1779SJakub Wojciech Klama return 493*134e1779SJakub Wojciech Klama 494*134e1779SJakub Wojciech Klama # Name supplied to create, mkdir, etc, may not contain /. 495*134e1779SJakub Wojciech Klama # Note that this test may fail for the wrong reason if /dir 496*134e1779SJakub Wojciech Klama # itself does not already exist, so first let's make /dir. 497*134e1779SJakub Wojciech Klama only_dotl = getbool(tstate.config, 'client', 'only_dotl', False) 498*134e1779SJakub Wojciech Klama with TestCase('mkdir', tstate) as tc: 499*134e1779SJakub Wojciech Klama clnt = tc.ccs() 500*134e1779SJakub Wojciech Klama if only_dotl and not clnt.supports(protocol.td.Tmkdir): 501*134e1779SJakub Wojciech Klama tc.skip('cannot test dot-L mkdir on {0}'.format(clnt.proto)) 502*134e1779SJakub Wojciech Klama try: 503*134e1779SJakub Wojciech Klama fid, qid = clnt.uxlookup(b'/dir', None) 504*134e1779SJakub Wojciech Klama tc.autoclunk(fid) 505*134e1779SJakub Wojciech Klama tstate.stop = True 506*134e1779SJakub Wojciech Klama tc.fail('found existing /dir after cleaning tree') 507*134e1779SJakub Wojciech Klama except RemoteError as err: 508*134e1779SJakub Wojciech Klama # we'll just assume it's "no such file or directory" 509*134e1779SJakub Wojciech Klama pass 510*134e1779SJakub Wojciech Klama if only_dotl: 511*134e1779SJakub Wojciech Klama qid = clnt.mkdir(clnt.rootfid, b'dir', 0o777, tstate.gid) 512*134e1779SJakub Wojciech Klama else: 513*134e1779SJakub Wojciech Klama qid, _ = clnt.create(clnt.rootfid, b'dir', 514*134e1779SJakub Wojciech Klama protocol.td.DMDIR | 0o777, 515*134e1779SJakub Wojciech Klama protocol.td.OREAD) 516*134e1779SJakub Wojciech Klama if qid.type != protocol.td.QTDIR: 517*134e1779SJakub Wojciech Klama tstate.stop = True 518*134e1779SJakub Wojciech Klama tc.fail('creating /dir: result is not a directory') 519*134e1779SJakub Wojciech Klama tc.trace('now attempting to create /dir/sub the wrong way') 520*134e1779SJakub Wojciech Klama try: 521*134e1779SJakub Wojciech Klama if only_dotl: 522*134e1779SJakub Wojciech Klama qid = clnt.mkdir(clnt.rootfid, b'dir/sub', 0o777, tstate.gid) 523*134e1779SJakub Wojciech Klama else: 524*134e1779SJakub Wojciech Klama qid, _ = clnt.create(clnt.rootfid, b'dir/sub', 525*134e1779SJakub Wojciech Klama protocol.td.DMDIR | 0o777, 526*134e1779SJakub Wojciech Klama protocol.td.OREAD) 527*134e1779SJakub Wojciech Klama # it's not clear what happened on the server at this point! 528*134e1779SJakub Wojciech Klama tc.trace("creating dir/sub (with embedded '/') should have " 529*134e1779SJakub Wojciech Klama 'failed but did not') 530*134e1779SJakub Wojciech Klama tstate.stop = True 531*134e1779SJakub Wojciech Klama fset = clnt.uxreaddir(b'/dir') 532*134e1779SJakub Wojciech Klama if 'sub' in fset: 533*134e1779SJakub Wojciech Klama tc.trace('(found our dir/sub detritus)') 534*134e1779SJakub Wojciech Klama clnt.uxremove(b'dir/sub', force=True) 535*134e1779SJakub Wojciech Klama fset = clnt.uxreaddir(b'/dir') 536*134e1779SJakub Wojciech Klama if 'sub' not in fset: 537*134e1779SJakub Wojciech Klama tc.trace('(successfully removed our dir/sub detritus)') 538*134e1779SJakub Wojciech Klama tstate.stop = False 539*134e1779SJakub Wojciech Klama tc.fail('created dir/sub as single directory with embedded slash') 540*134e1779SJakub Wojciech Klama except RemoteError as err: 541*134e1779SJakub Wojciech Klama # we'll just assume it's the right kind of error 542*134e1779SJakub Wojciech Klama tc.trace('invalid path dir/sub failed with: %s', str(err)) 543*134e1779SJakub Wojciech Klama tc.succ('embedded slash in mkdir correctly refused') 544*134e1779SJakub Wojciech Klama if tstate.stop: 545*134e1779SJakub Wojciech Klama return 546*134e1779SJakub Wojciech Klama 547*134e1779SJakub Wojciech Klama with TestCase('getattr/setattr', tstate) as tc: 548*134e1779SJakub Wojciech Klama # This test is not really thorough enough, need to test 549*134e1779SJakub Wojciech Klama # all combinations of settings. Should also test that 550*134e1779SJakub Wojciech Klama # old values are restored on failure, although it is not 551*134e1779SJakub Wojciech Klama # clear how to trigger failures. 552*134e1779SJakub Wojciech Klama clnt = tc.ccs() 553*134e1779SJakub Wojciech Klama if not clnt.supports(protocol.td.Tgetattr): 554*134e1779SJakub Wojciech Klama tc.skip('%s does not support Tgetattr', clnt) 555*134e1779SJakub Wojciech Klama fid, _, _, _ = clnt.uxopen(b'/dir/file', os.O_CREAT | os.O_RDWR, 0o666, 556*134e1779SJakub Wojciech Klama gid=tstate.gid) 557*134e1779SJakub Wojciech Klama tc.autoclunk(fid) 558*134e1779SJakub Wojciech Klama written = clnt.write(fid, 0, 'bytes\n') 559*134e1779SJakub Wojciech Klama if written != 6: 560*134e1779SJakub Wojciech Klama tc.trace('expected to write 6 bytes, actually wrote %d', written, 561*134e1779SJakub Wojciech Klama level=logging.WARN) 562*134e1779SJakub Wojciech Klama attrs = clnt.Tgetattr(fid) 563*134e1779SJakub Wojciech Klama #tc.trace('getattr: after write, before setattr: got %s', attrs) 564*134e1779SJakub Wojciech Klama if attrs.size != written: 565*134e1779SJakub Wojciech Klama tc.fail('getattr: expected size=%d, got size=%d', 566*134e1779SJakub Wojciech Klama written, attrs.size) 567*134e1779SJakub Wojciech Klama # now truncate, set mtime to (3,14), and check result 568*134e1779SJakub Wojciech Klama set_time_to = p9conn.Timespec(sec=0, nsec=140000000) 569*134e1779SJakub Wojciech Klama clnt.Tsetattr(fid, size=0, mtime=set_time_to) 570*134e1779SJakub Wojciech Klama attrs = clnt.Tgetattr(fid) 571*134e1779SJakub Wojciech Klama #tc.trace('getattr: after setattr: got %s', attrs) 572*134e1779SJakub Wojciech Klama if attrs.mtime.sec != set_time_to.sec or attrs.size != 0: 573*134e1779SJakub Wojciech Klama tc.fail('setattr: expected to get back mtime.sec={0}, size=0; ' 574*134e1779SJakub Wojciech Klama 'got mtime.sec={1}, size=' 575*134e1779SJakub Wojciech Klama '{1}'.format(set_time_to.sec, attrs.mtime.sec, attrs.size)) 576*134e1779SJakub Wojciech Klama # nsec is not as stable but let's check 577*134e1779SJakub Wojciech Klama if attrs.mtime.nsec != set_time_to.nsec: 578*134e1779SJakub Wojciech Klama tc.trace('setattr: expected to get back mtime_nsec=%d; ' 579*134e1779SJakub Wojciech Klama 'got %d', set_time_to.nsec, mtime_nsec) 580*134e1779SJakub Wojciech Klama tc.succ('able to set and see size and mtime') 581*134e1779SJakub Wojciech Klama 582*134e1779SJakub Wojciech Klama # this test should be much later, but we know the current 583*134e1779SJakub Wojciech Klama # server is broken... 584*134e1779SJakub Wojciech Klama with TestCase('rename adjusts other fids', tstate) as tc: 585*134e1779SJakub Wojciech Klama clnt = tc.ccs() 586*134e1779SJakub Wojciech Klama dirfid, _ = clnt.uxlookup(b'/dir') 587*134e1779SJakub Wojciech Klama tc.autoclunk(dirfid) 588*134e1779SJakub Wojciech Klama clnt.uxmkdir(b'd1', 0o777, tstate.gid, startdir=dirfid) 589*134e1779SJakub Wojciech Klama clnt.uxmkdir(b'd1/sub', 0o777, tstate.gid, startdir=dirfid) 590*134e1779SJakub Wojciech Klama d1fid, _ = clnt.uxlookup(b'd1', dirfid) 591*134e1779SJakub Wojciech Klama tc.autoclunk(d1fid) 592*134e1779SJakub Wojciech Klama subfid, _ = clnt.uxlookup(b'sub', d1fid) 593*134e1779SJakub Wojciech Klama tc.autoclunk(subfid) 594*134e1779SJakub Wojciech Klama fid, _, _, _ = clnt.uxopen(b'file', os.O_CREAT | os.O_RDWR, 595*134e1779SJakub Wojciech Klama 0o666, startdir=subfid, gid=tstate.gid) 596*134e1779SJakub Wojciech Klama tc.autoclunk(fid) 597*134e1779SJakub Wojciech Klama written = clnt.write(fid, 0, 'filedata\n') 598*134e1779SJakub Wojciech Klama if written != 9: 599*134e1779SJakub Wojciech Klama tc.trace('expected to write 9 bytes, actually wrote %d', written, 600*134e1779SJakub Wojciech Klama level=logging.WARN) 601*134e1779SJakub Wojciech Klama # Now if we rename /dir/d1 to /dir/d2, the fids for both 602*134e1779SJakub Wojciech Klama # sub/file and sub itself should still be usable. This 603*134e1779SJakub Wojciech Klama # holds for both Trename (Linux only) and Twstat based 604*134e1779SJakub Wojciech Klama # rename ops. 605*134e1779SJakub Wojciech Klama # 606*134e1779SJakub Wojciech Klama # Note that some servers may cache some number of files and/or 607*134e1779SJakub Wojciech Klama # diretories held open, so we should open many fids to wipe 608*134e1779SJakub Wojciech Klama # out the cache (XXX notyet). 609*134e1779SJakub Wojciech Klama if clnt.supports(protocol.td.Trename): 610*134e1779SJakub Wojciech Klama clnt.rename(d1fid, dirfid, name=b'd2') 611*134e1779SJakub Wojciech Klama else: 612*134e1779SJakub Wojciech Klama clnt.wstat(d1fid, name=b'd2') 613*134e1779SJakub Wojciech Klama try: 614*134e1779SJakub Wojciech Klama rofid, _, _, _ = clnt.uxopen(b'file', os.O_RDONLY, startdir=subfid) 615*134e1779SJakub Wojciech Klama clnt.clunk(rofid) 616*134e1779SJakub Wojciech Klama except RemoteError as err: 617*134e1779SJakub Wojciech Klama tc.fail('open file in renamed dir/d2/sub: {0}'.format(err)) 618*134e1779SJakub Wojciech Klama tc.succ() 619*134e1779SJakub Wojciech Klama 620*134e1779SJakub Wojciech Klama # Even if xattrwalk is supported by the protocol, it's optional 621*134e1779SJakub Wojciech Klama # on the server. 622*134e1779SJakub Wojciech Klama with TestCase('xattrwalk', tstate) as tc: 623*134e1779SJakub Wojciech Klama clnt = tc.ccs() 624*134e1779SJakub Wojciech Klama if not clnt.supports(protocol.td.Txattrwalk): 625*134e1779SJakub Wojciech Klama tc.skip('{0} does not support Txattrwalk'.format(clnt)) 626*134e1779SJakub Wojciech Klama dirfid, _ = clnt.uxlookup(b'/dir') 627*134e1779SJakub Wojciech Klama tc.autoclunk(dirfid) 628*134e1779SJakub Wojciech Klama try: 629*134e1779SJakub Wojciech Klama # need better tests... 630*134e1779SJakub Wojciech Klama attrfid, size = clnt.xattrwalk(dirfid) 631*134e1779SJakub Wojciech Klama tc.autoclunk(attrfid) 632*134e1779SJakub Wojciech Klama data = clnt.read(attrfid, 0, size) 633*134e1779SJakub Wojciech Klama tc.trace('xattrwalk with no name: data=%r', data) 634*134e1779SJakub Wojciech Klama tc.succ('xattrwalk size={0} datalen={1}'.format(size, len(data))) 635*134e1779SJakub Wojciech Klama except RemoteError as err: 636*134e1779SJakub Wojciech Klama tc.trace('xattrwalk on /dir: {0}'.format(err)) 637*134e1779SJakub Wojciech Klama tc.succ('xattrwalk apparently not implemented') 638*134e1779SJakub Wojciech Klama 639*134e1779SJakub Wojciech Klamaif __name__ == '__main__': 640*134e1779SJakub Wojciech Klama try: 641*134e1779SJakub Wojciech Klama sys.exit(main()) 642*134e1779SJakub Wojciech Klama except KeyboardInterrupt: 643*134e1779SJakub Wojciech Klama sys.exit('\nInterrupted') 644