xref: /freebsd/contrib/lib9p/pytest/client.py (revision 134e1779)
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