]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - contrib/fuse/runtests.py
58dfb5b482b2dd6e715319d19039149d04524960
[tahoe-lafs/tahoe-lafs.git] / contrib / fuse / runtests.py
1 #! /usr/bin/env python
2 '''
3 Unit and system tests for tahoe-fuse.
4 '''
5
6 # Note: It's always a SetupFailure, not a TestFailure if a webapi
7 # operation fails, because this does not indicate a fuse interface
8 # failure.
9
10 # TODO: Unmount after tests regardless of failure or success!
11
12 # TODO: Test mismatches between tahoe and fuse/posix.  What about nodes
13 # with crazy names ('\0', unicode, '/', '..')?  Huuuuge files?
14 # Huuuuge directories...  As tahoe approaches production quality, it'd
15 # be nice if the fuse interface did so also by hardening against such cases.
16
17 # FIXME: Only create / launch necessary nodes.  Do we still need an introducer and three nodes?
18
19 # FIXME: This framework might be replaceable with twisted.trial,
20 # especially the "layer" design, which is a bit cumbersome when
21 # using recursion to manage multiple clients.
22
23 # FIXME: Identify all race conditions (hint: starting clients, versus
24 # using the grid fs).
25
26 import sys, os, shutil, unittest, subprocess
27 import tempfile, re, time, random, httplib, urllib
28 #import traceback
29
30 from twisted.python import usage
31
32 if sys.platform.startswith('darwin'):
33     UNMOUNT_CMD = ['umount']
34 else:
35     # linux, and until we hear otherwise, all other platforms with fuse, by assumption
36     UNMOUNT_CMD = ['fusermount', '-u']
37
38 # Import fuse implementations:
39 #FuseDir = os.path.join('.', 'contrib', 'fuse')
40 #if not os.path.isdir(FuseDir):
41 #    raise SystemExit('''
42 #Could not find directory "%s".  Please run this script from the tahoe
43 #source base directory.
44 #''' % (FuseDir,))
45 FuseDir = '.'
46
47
48 ### Load each implementation
49 sys.path.append(os.path.join(FuseDir, 'impl_a'))
50 import tahoe_fuse as impl_a
51 sys.path.append(os.path.join(FuseDir, 'impl_b'))
52 import pyfuse.tahoe as impl_b
53 sys.path.append(os.path.join(FuseDir, 'impl_c'))
54 import blackmatch as impl_c
55
56 ### config info about each impl, including which make sense to run
57 implementations = {
58     'impl_a': dict(module=impl_a,
59                    mount_args=['--basedir', '%(nodedir)s', '%(mountpath)s', ],
60                    mount_wait=True,
61                    suites=['read', ]),
62     'impl_b': dict(module=impl_b,
63                    todo=True,
64                    mount_args=['--basedir', '%(nodedir)s', '%(mountpath)s', ],
65                    mount_wait=False,
66                    suites=['read', ]),
67     'impl_c': dict(module=impl_c,
68                    mount_args=['--cache-timeout', '0', '--root-uri', '%(root-uri)s',
69                                '--node-directory', '%(nodedir)s', '%(mountpath)s', ],
70                    mount_wait=True,
71                    suites=['read', 'write', ]),
72     'impl_c_no_split': dict(module=impl_c,
73                    mount_args=['--cache-timeout', '0', '--root-uri', '%(root-uri)s',
74                                '--no-split',
75                                '--node-directory', '%(nodedir)s', '%(mountpath)s', ],
76                    mount_wait=True,
77                    suites=['read', 'write', ]),
78     }
79
80 if sys.platform == 'darwin':
81     del implementations['impl_a']
82     del implementations['impl_b']
83
84 default_catch_up_pause = 0
85 if sys.platform == 'linux2':
86     default_catch_up_pause = 2
87
88 class FuseTestsOptions(usage.Options):
89     optParameters = [
90         ["test-type", None, "both",
91          "Type of test to run; unit, system or both"
92          ],
93         ["implementations", None, "all",
94          "Comma separated list of implementations to test, or 'all'"
95          ],
96         ["suites", None, "all",
97          "Comma separated list of test suites to run, or 'all'"
98          ],
99         ["tests", None, None,
100          "Comma separated list of specific tests to run"
101          ],
102         ["path-to-tahoe", None, "../../bin/tahoe",
103          "Which 'tahoe' script to use to create test nodes"],
104         ["tmp-dir", None, "/tmp",
105          "Where the test should create temporary files"],
106          # Note; this is '/tmp' because on leopard, tempfile.mkdtemp creates
107          # directories in a location which leads paths to exceed what macfuse
108          # can handle without leaking un-umount-able fuse processes.
109         ["catch-up-pause", None, str(default_catch_up_pause),
110          "Pause between tahoe operations and fuse tests thereon"],
111         ]
112     optFlags = [
113         ["debug-wait", None,
114          "Causes the test system to pause at various points, to facilitate debugging"],
115         ["web-open", None,
116          "Opens a web browser to the web ui at the start of each impl's tests"],
117         ["no-cleanup", False,
118          "Prevents the cleanup of the working directories, to allow analysis thereof"],
119          ]
120
121     def postOptions(self):
122         if self['suites'] == 'all':
123             self.suites = ['read', 'write']
124             # [ ] todo: deduce this from looking for test_ in dir(self)
125         else:
126             self.suites = map(str.strip, self['suites'].split(','))
127         if self['implementations'] == 'all':
128             self.implementations = implementations.keys()
129         else:
130             self.implementations = map(str.strip, self['implementations'].split(','))
131         if self['tests']:
132             self.tests = map(str.strip, self['tests'].split(','))
133         else:
134             self.tests = None
135         self.catch_up_pause = float(self['catch-up-pause'])
136
137 ### Main flow control:
138 def main(args):
139     config = FuseTestsOptions()
140     config.parseOptions(args[1:])
141
142     target = 'all'
143     if len(args) > 1:
144         target = args.pop(1)
145
146     test_type = config['test-type']
147     if test_type not in ('both', 'unit', 'system'):
148         raise usage.error('test-type %r not supported' % (test_type,))
149
150     if test_type in ('both', 'unit'):
151         run_unit_tests([args[0]])
152
153     if test_type in ('both', 'system'):
154         return run_system_test(config)
155
156
157 def run_unit_tests(argv):
158     print 'Running Unit Tests.'
159     try:
160         unittest.main(argv=argv)
161     except SystemExit, se:
162         pass
163     print 'Unit Tests complete.\n'
164
165
166 def run_system_test(config):
167     return SystemTest(config).run()
168
169 def drepr(obj):
170     r = repr(obj)
171     if len(r) > 200:
172         return '%s ... %s [%d]' % (r[:100], r[-100:], len(r))
173     else:
174         return r
175
176 ### System Testing:
177 class SystemTest (object):
178     def __init__(self, config):
179         self.config = config
180
181         # These members represent test state:
182         self.cliexec = None
183         self.testroot = None
184
185         # This test state is specific to the first client:
186         self.port = None
187         self.clientbase = None
188
189     ## Top-level flow control:
190     # These "*_layer" methods call each other in a linear fashion, using
191     # exception unwinding to do cleanup properly.  Each "layer" invokes
192     # a deeper layer, and each layer does its own cleanup upon exit.
193
194     def run(self):
195         print '\n*** Setting up system tests.'
196         try:
197             results = self.init_cli_layer()
198             print '\n*** System Tests complete:'
199             total_failures = todo_failures = 0
200             for result in results:
201                 impl_name, failures, total = result
202                 if implementations[impl_name].get('todo'):
203                     todo_failures += failures
204                 else:
205                     total_failures += failures
206                 print 'Implementation %s: %d failed out of %d.' % result           
207             if total_failures:
208                 print '%s total failures, %s todo' % (total_failures, todo_failures)
209                 return 1
210             else:
211                 return 0
212         except SetupFailure, sfail:
213             print
214             print sfail
215             print '\n*** System Tests were not successfully completed.' 
216             return 1
217
218     def maybe_wait(self, msg='waiting', or_if_webopen=False):
219         if self.config['debug-wait'] or or_if_webopen and self.config['web-open']:
220             print msg
221             raw_input()
222
223     def maybe_webopen(self, where=None):
224         if self.config['web-open']:
225             import webbrowser
226             url = self.weburl
227             if where is not None:
228                 url += urllib.quote(where)
229             webbrowser.open(url)
230
231     def maybe_pause(self):
232         time.sleep(self.config.catch_up_pause)
233
234     def init_cli_layer(self):
235         '''This layer finds the appropriate tahoe executable.'''
236         #self.cliexec = os.path.join('.', 'bin', 'tahoe')
237         self.cliexec = self.config['path-to-tahoe']
238         version = self.run_tahoe('--version')
239         print 'Using %r with version:\n%s' % (self.cliexec, version.rstrip())
240
241         return self.create_testroot_layer()
242
243     def create_testroot_layer(self):
244         print 'Creating test base directory.'
245         #self.testroot = tempfile.mkdtemp(prefix='tahoe_fuse_test_')
246         #self.testroot = tempfile.mkdtemp(prefix='tahoe_fuse_test_', dir='/tmp/')
247         tmpdir = self.config['tmp-dir']
248         if tmpdir:
249             self.testroot = tempfile.mkdtemp(prefix='tahoe_fuse_test_', dir=tmpdir)
250         else:
251             self.testroot = tempfile.mkdtemp(prefix='tahoe_fuse_test_')
252         try:
253             return self.launch_introducer_layer()
254         finally:
255             if not self.config['no-cleanup']:
256                 print 'Cleaning up test root directory.'
257                 try:
258                     shutil.rmtree(self.testroot)
259                 except Exception, e:
260                     print 'Exception removing test root directory: %r' % (self.testroot, )
261                     print 'Ignoring cleanup exception: %r' % (e,)
262             else:
263                 print 'Leaving test root directory: %r' % (self.testroot, )
264
265
266     def launch_introducer_layer(self):
267         print 'Launching introducer.'
268         introbase = os.path.join(self.testroot, 'introducer')
269
270         # NOTE: We assume if tahoe exits with non-zero status, no separate
271         # tahoe child process is still running.
272         createoutput = self.run_tahoe('create-introducer', '--basedir', introbase)
273
274         self.check_tahoe_output(createoutput, ExpectedCreationOutput, introbase)
275
276         startoutput = self.run_tahoe('start', '--basedir', introbase)
277         try:
278             self.check_tahoe_output(startoutput, ExpectedStartOutput, introbase)
279
280             return self.launch_clients_layer(introbase)
281
282         finally:
283             print 'Stopping introducer node.'
284             self.stop_node(introbase)
285
286     def set_tahoe_option(self, base, key, value):
287         import re
288
289         filename = os.path.join(base, 'tahoe.cfg')
290         content = open(filename).read()
291         content = re.sub('%s = (.+)' % key, '%s = %s' % (key, value), content)
292         open(filename, 'w').write(content)
293
294     TotalClientsNeeded = 3
295     def launch_clients_layer(self, introbase, clientnum = 0):
296         if clientnum >= self.TotalClientsNeeded:
297             self.maybe_wait('waiting (launched clients)')
298             ret = self.create_test_dirnode_layer()
299             self.maybe_wait('waiting (ran tests)', or_if_webopen=True)
300             return ret
301
302         tmpl = 'Launching client %d of %d.'
303         print tmpl % (clientnum,
304                       self.TotalClientsNeeded)
305
306         base = os.path.join(self.testroot, 'client_%d' % (clientnum,))
307
308         output = self.run_tahoe('create-node', '--basedir', base)
309         self.check_tahoe_output(output, ExpectedCreationOutput, base)
310
311         if clientnum == 0:
312             # The first client is special:
313             self.clientbase = base
314             self.port = random.randrange(1024, 2**15)
315
316             self.set_tahoe_option(base, 'web.port', 'tcp:%d:interface=127.0.0.1' % self.port)
317
318             self.weburl = "http://127.0.0.1:%d/" % (self.port,)
319             print self.weburl
320         else:
321             self.set_tahoe_option(base, 'web.port', '')
322
323         introfurl = os.path.join(introbase, 'introducer.furl')
324
325         furl = open(introfurl).read().strip()
326         self.set_tahoe_option(base, 'introducer.furl', furl)
327
328         # NOTE: We assume if tahoe exist with non-zero status, no separate
329         # tahoe child process is still running.
330         startoutput = self.run_tahoe('start', '--basedir', base)
331         try:
332             self.check_tahoe_output(startoutput, ExpectedStartOutput, base)
333
334             return self.launch_clients_layer(introbase, clientnum+1)
335
336         finally:
337             print 'Stopping client node %d.' % (clientnum,)
338             self.stop_node(base)
339
340     def create_test_dirnode_layer(self):
341         print 'Creating test dirnode.'
342
343         cap = self.create_dirnode()
344
345         f = open(os.path.join(self.clientbase, 'private', 'root_dir.cap'), 'w')
346         f.write(cap)
347         f.close()
348
349         return self.mount_fuse_layer(cap)
350
351     def mount_fuse_layer(self, root_uri):
352         mpbase = os.path.join(self.testroot, 'mountpoint')
353         os.mkdir(mpbase)
354         results = []
355
356         if self.config['debug-wait']:
357             ImplProcessManager.debug_wait = True
358
359         #for name, kwargs in implementations.items():
360         for name in self.config.implementations:
361             kwargs = implementations[name]
362             #print 'instantiating %s: %r' % (name, kwargs)
363             implprocmgr = ImplProcessManager(name, **kwargs)
364             print '\n*** Testing impl: %r' % (implprocmgr.name)
365             implprocmgr.configure(self.clientbase, mpbase)
366             implprocmgr.mount()
367             try:
368                 failures, total = self.run_test_layer(root_uri, implprocmgr)
369                 result = (implprocmgr.name, failures, total)
370                 tmpl = '\n*** Test Results implementation %s: %d failed out of %d.'
371                 print tmpl % result
372                 results.append(result)
373             finally:
374                 implprocmgr.umount()
375         return results
376
377     def run_test_layer(self, root_uri, iman):
378         self.maybe_webopen('uri/'+root_uri)
379         failures = 0
380         testnum = 0
381         numtests = 0
382         if self.config.tests:
383             tests = self.config.tests
384         else:
385             tests = list(set(self.config.suites).intersection(set(iman.suites)))
386         self.maybe_wait('waiting (about to run tests)')
387         for test in tests:
388             testnames = [n for n in sorted(dir(self)) if n.startswith('test_'+test)]
389             numtests += len(testnames)
390             print 'running %s %r tests' % (len(testnames), test,)
391             for testname in testnames:
392                 testnum += 1
393                 print '\n*** Running test #%d: %s' % (testnum, testname)
394                 try:
395                     testcap = self.create_dirnode()
396                     dirname = '%s_%s' % (iman.name, testname)
397                     self.attach_node(root_uri, testcap, dirname)
398                     method = getattr(self, testname)
399                     method(testcap, testdir = os.path.join(iman.mountpath, dirname))
400                     print 'Test succeeded.'
401                 except TestFailure, f:
402                     print f
403                     #print traceback.format_exc()
404                     failures += 1
405                 except:
406                     print 'Error in test code...  Cleaning up.'
407                     raise
408         return (failures, numtests)
409
410     # Tests:
411     def test_read_directory_existence(self, testcap, testdir):
412         if not wrap_os_error(os.path.isdir, testdir):
413             raise TestFailure('Attached test directory not found: %r', testdir)
414
415     def test_read_empty_directory_listing(self, testcap, testdir):
416         listing = wrap_os_error(os.listdir, testdir)
417         if listing:
418             raise TestFailure('Expected empty directory, found: %r', listing)
419
420     def test_read_directory_listing(self, testcap, testdir):
421         names = []
422         filesizes = {}
423
424         for i in range(3):
425             fname = 'file_%d' % (i,)
426             names.append(fname)
427             body = 'Hello World #%d!' % (i,)
428             filesizes[fname] = len(body)
429
430             cap = self.webapi_call('PUT', '/uri', body)
431             self.attach_node(testcap, cap, fname)
432
433             dname = 'dir_%d' % (i,)
434             names.append(dname)
435
436             cap = self.create_dirnode()
437             self.attach_node(testcap, cap, dname)
438
439         names.sort()
440
441         listing = wrap_os_error(os.listdir, testdir)
442         listing.sort()
443
444         if listing != names:
445             tmpl = 'Expected directory list containing %r but fuse gave %r'
446             raise TestFailure(tmpl, names, listing)
447
448         for file, size in filesizes.items():
449             st = wrap_os_error(os.stat, os.path.join(testdir, file))
450             if st.st_size != size:
451                 tmpl = 'Expected %r size of %r but fuse returned %r'
452                 raise TestFailure(tmpl, file, size, st.st_size)
453
454     def test_read_file_contents(self, testcap, testdir):
455         name = 'hw.txt'
456         body = 'Hello World!'
457
458         cap = self.webapi_call('PUT', '/uri', body)
459         self.attach_node(testcap, cap, name)
460
461         path = os.path.join(testdir, name)
462         try:
463             found = open(path, 'r').read()
464         except Exception, err:
465             tmpl = 'Could not read file contents of %r: %r'
466             raise TestFailure(tmpl, path, err)
467
468         if found != body:
469             tmpl = 'Expected file contents %r but found %r'
470             raise TestFailure(tmpl, body, found)
471
472     def test_read_in_random_order(self, testcap, testdir):
473         sz = 2**20
474         bs = 2**10
475         assert(sz % bs == 0)
476         name = 'random_read_order'
477         body = os.urandom(sz)
478
479         cap = self.webapi_call('PUT', '/uri', body)
480         self.attach_node(testcap, cap, name)
481
482         # XXX this should also do a test where sz%bs != 0, so that it correctly tests
483         # the edge case where the last read is a 'short' block
484         path = os.path.join(testdir, name)
485         try:
486             fsize = os.path.getsize(path)
487             if fsize != len(body):
488                 tmpl = 'Expected file size %s but found %s'
489                 raise TestFailure(tmpl, len(body), fsize)
490         except Exception, err:
491             tmpl = 'Could not read file size for %r: %r'
492             raise TestFailure(tmpl, path, err)
493
494         try:
495             f = open(path, 'r')
496             posns = range(0,sz,bs)
497             random.shuffle(posns)
498             data = [None] * (sz/bs)
499             for p in posns:
500                 f.seek(p)
501                 data[p/bs] = f.read(bs)
502             found = ''.join(data)
503         except Exception, err:
504             tmpl = 'Could not read file %r: %r'
505             raise TestFailure(tmpl, path, err)
506
507         if found != body:
508             tmpl = 'Expected file contents %s but found %s'
509             raise TestFailure(tmpl, drepr(body), drepr(found))
510
511     def get_file(self, dircap, path):
512         body = self.webapi_call('GET', '/uri/%s/%s' % (dircap, path))
513         return body
514
515     def test_write_tiny_file(self, testcap, testdir):
516         self._write_test_linear(testcap, testdir, name='tiny.junk', bs=2**9, sz=2**9)
517
518     def test_write_linear_small_writes(self, testcap, testdir):
519         self._write_test_linear(testcap, testdir, name='large_linear.junk', bs=2**9, sz=2**20)
520
521     def test_write_linear_large_writes(self, testcap, testdir):
522         # at least on the mac, large io block sizes are reduced to 64k writes through fuse
523         self._write_test_linear(testcap, testdir, name='small_linear.junk', bs=2**18, sz=2**20)
524
525     def _write_test_linear(self, testcap, testdir, name, bs, sz):
526         body = os.urandom(sz)
527         try:
528             path = os.path.join(testdir, name)
529             f = file(path, 'w')
530         except Exception, err:
531             tmpl = 'Could not open file for write at %r: %r'
532             raise TestFailure(tmpl, path, err)
533         try:
534             for posn in range(0,sz,bs):
535                 f.write(body[posn:posn+bs])
536             f.close()
537         except Exception, err:
538             tmpl = 'Could not write to file %r: %r'
539             raise TestFailure(tmpl, path, err)
540
541         self.maybe_pause()
542         self._check_write(testcap, name, body)
543
544     def _check_write(self, testcap, name, expected_body):
545         uploaded_body = self.get_file(testcap, name)
546         if uploaded_body != expected_body:
547             tmpl = 'Expected file contents %s but found %s'
548             raise TestFailure(tmpl, drepr(expected_body), drepr(uploaded_body))
549
550     def test_write_overlapping_small_writes(self, testcap, testdir):
551         self._write_test_overlap(testcap, testdir, name='large_overlap', bs=2**9, sz=2**20)
552
553     def test_write_overlapping_large_writes(self, testcap, testdir):
554         self._write_test_overlap(testcap, testdir, name='small_overlap', bs=2**18, sz=2**20)
555
556     def _write_test_overlap(self, testcap, testdir, name, bs, sz):
557         body = os.urandom(sz)
558         try:
559             path = os.path.join(testdir, name)
560             f = file(path, 'w')
561         except Exception, err:
562             tmpl = 'Could not open file for write at %r: %r'
563             raise TestFailure(tmpl, path, err)
564         try:
565             for posn in range(0,sz,bs):
566                 start = max(0, posn-bs)
567                 end = min(sz, posn+bs)
568                 f.seek(start)
569                 f.write(body[start:end])
570             f.close()
571         except Exception, err:
572             tmpl = 'Could not write to file %r: %r'
573             raise TestFailure(tmpl, path, err)
574
575         self.maybe_pause()
576         self._check_write(testcap, name, body)
577
578
579     def test_write_random_scatter(self, testcap, testdir):
580         sz = 2**20
581         name = 'random_scatter'
582         body = os.urandom(sz)
583
584         def rsize(sz=sz):
585             return min(int(random.paretovariate(.25)), sz/12)
586
587         # first chop up whole file into random sized chunks
588         slices = []
589         posn = 0
590         while posn < sz:
591             size = rsize()
592             slices.append( (posn, body[posn:posn+size]) )
593             posn += size
594         random.shuffle(slices) # and randomise their order
595
596         try:
597             path = os.path.join(testdir, name)
598             f = file(path, 'w')
599         except Exception, err:
600             tmpl = 'Could not open file for write at %r: %r'
601             raise TestFailure(tmpl, path, err)
602         try:
603             # write all slices: we hence know entire file is ultimately written
604             # write random excerpts: this provides for mixed and varied overlaps
605             for posn,slice in slices:
606                 f.seek(posn)
607                 f.write(slice)
608                 rposn = random.randint(0,sz)
609                 f.seek(rposn)
610                 f.write(body[rposn:rposn+rsize()])
611             f.close()
612         except Exception, err:
613             tmpl = 'Could not write to file %r: %r'
614             raise TestFailure(tmpl, path, err)
615
616         self.maybe_pause()
617         self._check_write(testcap, name, body)
618
619     def test_write_partial_overwrite(self, testcap, testdir):
620         name = 'partial_overwrite'
621         body = '_'*132
622         overwrite = '^'*8
623         position = 26
624
625         def write_file(path, mode, contents, position=None):
626             try:
627                 f = file(path, mode)
628                 if position is not None:
629                     f.seek(position)
630                 f.write(contents)
631                 f.close()
632             except Exception, err:
633                 tmpl = 'Could not write to file %r: %r'
634                 raise TestFailure(tmpl, path, err)
635
636         def read_file(path):
637             try:
638                 f = file(path, 'rb')
639                 contents = f.read()
640                 f.close()
641             except Exception, err:
642                 tmpl = 'Could not read file %r: %r'
643                 raise TestFailure(tmpl, path, err)
644             return contents
645
646         path = os.path.join(testdir, name)
647         #write_file(path, 'w', body)
648
649         cap = self.webapi_call('PUT', '/uri', body)
650         self.attach_node(testcap, cap, name)
651         self.maybe_pause()
652
653         contents = read_file(path)
654         if contents != body:
655             raise TestFailure('File contents mismatch (%r) %r v.s. %r', path, contents, body)
656
657         write_file(path, 'r+', overwrite, position)
658         contents = read_file(path)
659         expected = body[:position] + overwrite + body[position+len(overwrite):]
660         if contents != expected:
661             raise TestFailure('File contents mismatch (%r) %r v.s. %r', path, contents, expected)
662
663
664     # Utilities:
665     def run_tahoe(self, *args):
666         realargs = ('tahoe',) + args
667         status, output = gather_output(realargs, executable=self.cliexec)
668         if status != 0:
669             tmpl = 'The tahoe cli exited with nonzero status.\n'
670             tmpl += 'Executable: %r\n'
671             tmpl += 'Command arguments: %r\n'
672             tmpl += 'Exit status: %r\n'
673             tmpl += 'Output:\n%s\n[End of tahoe output.]\n'
674             raise SetupFailure(tmpl,
675                                     self.cliexec,
676                                     realargs,
677                                     status,
678                                     output)
679         return output
680
681     def check_tahoe_output(self, output, expected, expdir):
682         ignorable_lines = map(re.compile, [
683             '.*site-packages/zope\.interface.*\.egg/zope/__init__.py:3: UserWarning: Module twisted was already imported from .*egg is being added to sys.path',
684             '  import pkg_resources',
685             ])
686         def ignore_line(line):
687             for ignorable_line in ignorable_lines:
688                 if ignorable_line.match(line):
689                     return True
690             else:
691                 return False
692         output = '\n'.join( [ line 
693                               for line in output.split('\n')+['']
694                               #if line not in ignorable_lines ] )
695                               if not ignore_line(line) ] )
696         m = re.match(expected, output, re.M)
697         if m is None:
698             tmpl = 'The output of tahoe did not match the expectation:\n'
699             tmpl += 'Expected regex: %s\n'
700             tmpl += 'Actual output: %r\n'
701             self.warn(tmpl, expected, output)
702
703         elif expdir != m.group('path'):
704             tmpl = 'The output of tahoe refers to an unexpected directory:\n'
705             tmpl += 'Expected directory: %r\n'
706             tmpl += 'Actual directory: %r\n'
707             self.warn(tmpl, expdir, m.group(1))
708
709     def stop_node(self, basedir):
710         try:
711             self.run_tahoe('stop', '--basedir', basedir)
712         except Exception, e:
713             print 'Failed to stop tahoe node.'
714             print 'Ignoring cleanup exception:'
715             # Indent the exception description:
716             desc = str(e).rstrip()
717             print '  ' + desc.replace('\n', '\n  ')
718
719     def webapi_call(self, method, path, body=None, **options):
720         if options:
721             path = path + '?' + ('&'.join(['%s=%s' % kv for kv in options.items()]))
722
723         conn = httplib.HTTPConnection('127.0.0.1', self.port)
724         conn.request(method, path, body = body)
725         resp = conn.getresponse()
726
727         if resp.status != 200:
728             tmpl = 'A webapi operation failed.\n'
729             tmpl += 'Request: %r %r\n'
730             tmpl += 'Body:\n%s\n'
731             tmpl += 'Response:\nStatus %r\nBody:\n%s'
732             raise SetupFailure(tmpl,
733                                     method, path,
734                                     body or '',
735                                     resp.status, body)
736
737         return resp.read()
738
739     def create_dirnode(self):
740         return self.webapi_call('PUT', '/uri', t='mkdir').strip()
741
742     def attach_node(self, dircap, childcap, childname):
743         body = self.webapi_call('PUT',
744                                 '/uri/%s/%s' % (dircap, childname),
745                                 body = childcap,
746                                 t = 'uri',
747                                 replace = 'false')
748         assert body.strip() == childcap, `body, dircap, childcap, childname`
749
750     def polling_operation(self, operation, polldesc, timeout = 10.0, pollinterval = 0.2):
751         totaltime = timeout # Fudging for edge-case SetupFailure description...
752
753         totalattempts = int(timeout / pollinterval)
754
755         starttime = time.time()
756         for attempt in range(totalattempts):
757             opstart = time.time()
758
759             try:
760                 result = operation()
761             except KeyboardInterrupt, e:
762                 raise
763             except Exception, e:
764                 result = False
765
766             totaltime = time.time() - starttime
767
768             if result is not False:
769                 #tmpl = '(Polling took over %.2f seconds.)'
770                 #print tmpl % (totaltime,)
771                 return result
772
773             elif totaltime > timeout:
774                 break
775
776             else:
777                 opdelay = time.time() - opstart
778                 realinterval = max(0., pollinterval - opdelay)
779
780                 #tmpl = '(Poll attempt %d failed after %.2f seconds, sleeping %.2f seconds.)'
781                 #print tmpl % (attempt+1, opdelay, realinterval)
782                 time.sleep(realinterval)
783
784
785         tmpl = 'Timeout while polling for: %s\n'
786         tmpl += 'Waited %.2f seconds (%d polls).'
787         raise SetupFailure(tmpl, polldesc, totaltime, attempt+1)
788
789     def warn(self, tmpl, *args):
790         print ('Test Warning: ' + tmpl) % args
791
792
793 # SystemTest Exceptions:
794 class Failure (Exception):
795     def __init__(self, tmpl, *args):
796         msg = self.Prefix + (tmpl % args)
797         Exception.__init__(self, msg)
798
799 class SetupFailure (Failure):
800     Prefix = 'Setup Failure - The test framework encountered an error:\n'
801
802 class TestFailure (Failure):
803     Prefix = 'TestFailure: '
804
805
806 ### Unit Tests:
807 class Impl_A_UnitTests (unittest.TestCase):
808     '''Tests small stand-alone functions.'''
809     def test_canonicalize_cap(self):
810         iopairs = [('http://127.0.0.1:3456/uri/URI:DIR2:yar9nnzsho6czczieeesc65sry:upp1pmypwxits3w9izkszgo1zbdnsyk3nm6h7e19s7os7s6yhh9y',
811                     'URI:DIR2:yar9nnzsho6czczieeesc65sry:upp1pmypwxits3w9izkszgo1zbdnsyk3nm6h7e19s7os7s6yhh9y'),
812                    ('http://127.0.0.1:3456/uri/URI%3ACHK%3Ak7ktp1qr7szmt98s1y3ha61d9w%3A8tiy8drttp65u79pjn7hs31po83e514zifdejidyeo1ee8nsqfyy%3A3%3A12%3A242?filename=welcome.html',
813                     'URI:CHK:k7ktp1qr7szmt98s1y3ha61d9w:8tiy8drttp65u79pjn7hs31po83e514zifdejidyeo1ee8nsqfyy:3:12:242?filename=welcome.html')]
814
815         for input, output in iopairs:
816             result = impl_a.canonicalize_cap(input)
817             self.failUnlessEqual(output, result, 'input == %r' % (input,))
818
819
820
821 ### Misc:
822 class ImplProcessManager(object):
823     debug_wait = False
824
825     def __init__(self, name, module, mount_args, mount_wait, suites, todo=False):
826         self.name = name
827         self.module = module
828         self.script = module.__file__
829         self.mount_args = mount_args
830         self.mount_wait = mount_wait
831         self.suites = suites
832         self.todo = todo
833
834     def maybe_wait(self, msg='waiting'):
835         if self.debug_wait:
836             print msg
837             raw_input()
838
839     def configure(self, client_nodedir, mountpoint):
840         self.client_nodedir = client_nodedir
841         self.mountpath = os.path.join(mountpoint, self.name)
842         os.mkdir(self.mountpath)
843
844     def mount(self):
845         print 'Mounting implementation: %s (%s)' % (self.name, self.script)
846
847         rootdirfile = os.path.join(self.client_nodedir, 'private', 'root_dir.cap')
848         root_uri = file(rootdirfile, 'r').read().strip()
849         fields = {'mountpath': self.mountpath,
850                   'nodedir': self.client_nodedir,
851                   'root-uri': root_uri,
852                  }
853         args = ['python', self.script] + [ arg%fields for arg in self.mount_args ]
854         print ' '.join(args)
855         self.maybe_wait('waiting (about to launch fuse)')
856
857         if self.mount_wait:
858             exitcode, output = gather_output(args)
859             if exitcode != 0:
860                 tmpl = '%r failed to launch:\n'
861                 tmpl += 'Exit Status: %r\n'
862                 tmpl += 'Output:\n%s\n'
863                 raise SetupFailure(tmpl, self.script, exitcode, output)
864         else:
865             self.proc = subprocess.Popen(args)
866
867     def umount(self):
868         print 'Unmounting implementation: %s' % (self.name,)
869         args = UNMOUNT_CMD + [self.mountpath]
870         print args
871         self.maybe_wait('waiting (unmount)')
872         #print os.system('ls -l '+self.mountpath)
873         ec, out = gather_output(args)
874         if ec != 0 or out:
875             tmpl = '%r failed to unmount:\n' % (' '.join(UNMOUNT_CMD),)
876             tmpl += 'Arguments: %r\n'
877             tmpl += 'Exit Status: %r\n'
878             tmpl += 'Output:\n%s\n'
879             raise SetupFailure(tmpl, args, ec, out)
880
881
882 def gather_output(*args, **kwargs):
883     '''
884     This expects the child does not require input and that it closes
885     stdout/err eventually.
886     '''
887     p = subprocess.Popen(stdout = subprocess.PIPE,
888                          stderr = subprocess.STDOUT,
889                          *args,
890                          **kwargs)
891     output = p.stdout.read()
892     exitcode = p.wait()
893     return (exitcode, output)
894
895
896 def wrap_os_error(meth, *args):
897     try:
898         return meth(*args)
899     except os.error, e:
900         raise TestFailure('%s', e)
901
902
903 ExpectedCreationOutput = r'(introducer|client) created in (?P<path>.*?)\n'
904 ExpectedStartOutput = r'(.*\n)*STARTING (?P<path>.*?)\n(introducer|client) node probably started'
905
906
907 if __name__ == '__main__':
908     sys.exit(main(sys.argv))