3 Unit and system tests for tahoe-fuse.
6 # Note: It's always a SetupFailure, not a TestFailure if a webapi
7 # operation fails, because this does not indicate a fuse interface
10 # TODO: Unmount after tests regardless of failure or success!
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.
17 # FIXME: Only create / launch necessary nodes. Do we still need an introducer and three nodes?
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.
23 # FIXME: Identify all race conditions (hint: starting clients, versus
26 import sys, os, shutil, unittest, subprocess
27 import tempfile, re, time, random, httplib, urllib
30 from twisted.python import usage
32 if sys.platform.startswith('darwin'):
33 UNMOUNT_CMD = ['umount']
35 # linux, and until we hear otherwise, all other platforms with fuse, by assumption
36 UNMOUNT_CMD = ['fusermount', '-u']
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.
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
56 ### config info about each impl, including which make sense to run
58 'impl_a': dict(module=impl_a,
59 mount_args=['--basedir', '%(nodedir)s', '%(mountpath)s', ],
62 'impl_b': dict(module=impl_b,
64 mount_args=['--basedir', '%(nodedir)s', '%(mountpath)s', ],
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', ],
71 suites=['read', 'write', ]),
72 'impl_c_no_split': dict(module=impl_c,
73 mount_args=['--cache-timeout', '0', '--root-uri', '%(root-uri)s',
75 '--node-directory', '%(nodedir)s', '%(mountpath)s', ],
77 suites=['read', 'write', ]),
80 if sys.platform == 'darwin':
81 del implementations['impl_a']
82 del implementations['impl_b']
84 default_catch_up_pause = 0
85 if sys.platform == 'linux2':
86 default_catch_up_pause = 2
88 class FuseTestsOptions(usage.Options):
90 ["test-type", None, "both",
91 "Type of test to run; unit, system or both"
93 ["implementations", None, "all",
94 "Comma separated list of implementations to test, or 'all'"
96 ["suites", None, "all",
97 "Comma separated list of test suites to run, or 'all'"
100 "Comma separated list of specific tests to run"
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"],
114 "Causes the test system to pause at various points, to facilitate debugging"],
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"],
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)
126 self.suites = map(str.strip, self['suites'].split(','))
127 if self['implementations'] == 'all':
128 self.implementations = implementations.keys()
130 self.implementations = map(str.strip, self['implementations'].split(','))
132 self.tests = map(str.strip, self['tests'].split(','))
135 self.catch_up_pause = float(self['catch-up-pause'])
137 ### Main flow control:
139 config = FuseTestsOptions()
140 config.parseOptions(args[1:])
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,))
150 if test_type in ('both', 'unit'):
151 run_unit_tests([args[0]])
153 if test_type in ('both', 'system'):
154 return run_system_test(config)
157 def run_unit_tests(argv):
158 print 'Running Unit Tests.'
160 unittest.main(argv=argv)
161 except SystemExit, se:
163 print 'Unit Tests complete.\n'
166 def run_system_test(config):
167 return SystemTest(config).run()
172 return '%s ... %s [%d]' % (r[:100], r[-100:], len(r))
177 class SystemTest (object):
178 def __init__(self, config):
181 # These members represent test state:
185 # This test state is specific to the first client:
187 self.clientbase = None
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.
195 print '\n*** Setting up system tests.'
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
205 total_failures += failures
206 print 'Implementation %s: %d failed out of %d.' % result
208 print '%s total failures, %s todo' % (total_failures, todo_failures)
212 except SetupFailure, sfail:
215 print '\n*** System Tests were not successfully completed.'
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']:
223 def maybe_webopen(self, where=None):
224 if self.config['web-open']:
227 if where is not None:
228 url += urllib.quote(where)
231 def maybe_pause(self):
232 time.sleep(self.config.catch_up_pause)
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())
241 return self.create_testroot_layer()
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']
249 self.testroot = tempfile.mkdtemp(prefix='tahoe_fuse_test_', dir=tmpdir)
251 self.testroot = tempfile.mkdtemp(prefix='tahoe_fuse_test_')
253 return self.launch_introducer_layer()
255 if not self.config['no-cleanup']:
256 print 'Cleaning up test root directory.'
258 shutil.rmtree(self.testroot)
260 print 'Exception removing test root directory: %r' % (self.testroot, )
261 print 'Ignoring cleanup exception: %r' % (e,)
263 print 'Leaving test root directory: %r' % (self.testroot, )
266 def launch_introducer_layer(self):
267 print 'Launching introducer.'
268 introbase = os.path.join(self.testroot, 'introducer')
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)
274 self.check_tahoe_output(createoutput, ExpectedCreationOutput, introbase)
276 startoutput = self.run_tahoe('start', '--basedir', introbase)
278 self.check_tahoe_output(startoutput, ExpectedStartOutput, introbase)
280 return self.launch_clients_layer(introbase)
283 print 'Stopping introducer node.'
284 self.stop_node(introbase)
286 def set_tahoe_option(self, base, key, value):
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)
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)
302 tmpl = 'Launching client %d of %d.'
303 print tmpl % (clientnum,
304 self.TotalClientsNeeded)
306 base = os.path.join(self.testroot, 'client_%d' % (clientnum,))
308 output = self.run_tahoe('create-node', '--basedir', base)
309 self.check_tahoe_output(output, ExpectedCreationOutput, base)
312 # The first client is special:
313 self.clientbase = base
314 self.port = random.randrange(1024, 2**15)
316 self.set_tahoe_option(base, 'web.port', 'tcp:%d:interface=127.0.0.1' % self.port)
318 self.weburl = "http://127.0.0.1:%d/" % (self.port,)
321 self.set_tahoe_option(base, 'web.port', '')
323 introfurl = os.path.join(introbase, 'introducer.furl')
325 furl = open(introfurl).read().strip()
326 self.set_tahoe_option(base, 'introducer.furl', furl)
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)
332 self.check_tahoe_output(startoutput, ExpectedStartOutput, base)
334 return self.launch_clients_layer(introbase, clientnum+1)
337 print 'Stopping client node %d.' % (clientnum,)
340 def create_test_dirnode_layer(self):
341 print 'Creating test dirnode.'
343 cap = self.create_dirnode()
345 f = open(os.path.join(self.clientbase, 'private', 'root_dir.cap'), 'w')
349 return self.mount_fuse_layer(cap)
351 def mount_fuse_layer(self, root_uri):
352 mpbase = os.path.join(self.testroot, 'mountpoint')
356 if self.config['debug-wait']:
357 ImplProcessManager.debug_wait = True
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)
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.'
372 results.append(result)
377 def run_test_layer(self, root_uri, iman):
378 self.maybe_webopen('uri/'+root_uri)
382 if self.config.tests:
383 tests = self.config.tests
385 tests = list(set(self.config.suites).intersection(set(iman.suites)))
386 self.maybe_wait('waiting (about to run 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:
393 print '\n*** Running test #%d: %s' % (testnum, testname)
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:
403 #print traceback.format_exc()
406 print 'Error in test code... Cleaning up.'
408 return (failures, numtests)
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)
415 def test_read_empty_directory_listing(self, testcap, testdir):
416 listing = wrap_os_error(os.listdir, testdir)
418 raise TestFailure('Expected empty directory, found: %r', listing)
420 def test_read_directory_listing(self, testcap, testdir):
425 fname = 'file_%d' % (i,)
427 body = 'Hello World #%d!' % (i,)
428 filesizes[fname] = len(body)
430 cap = self.webapi_call('PUT', '/uri', body)
431 self.attach_node(testcap, cap, fname)
433 dname = 'dir_%d' % (i,)
436 cap = self.create_dirnode()
437 self.attach_node(testcap, cap, dname)
441 listing = wrap_os_error(os.listdir, testdir)
445 tmpl = 'Expected directory list containing %r but fuse gave %r'
446 raise TestFailure(tmpl, names, listing)
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)
454 def test_read_file_contents(self, testcap, testdir):
456 body = 'Hello World!'
458 cap = self.webapi_call('PUT', '/uri', body)
459 self.attach_node(testcap, cap, name)
461 path = os.path.join(testdir, name)
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)
469 tmpl = 'Expected file contents %r but found %r'
470 raise TestFailure(tmpl, body, found)
472 def test_read_in_random_order(self, testcap, testdir):
476 name = 'random_read_order'
477 body = os.urandom(sz)
479 cap = self.webapi_call('PUT', '/uri', body)
480 self.attach_node(testcap, cap, name)
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)
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)
496 posns = range(0,sz,bs)
497 random.shuffle(posns)
498 data = [None] * (sz/bs)
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)
508 tmpl = 'Expected file contents %s but found %s'
509 raise TestFailure(tmpl, drepr(body), drepr(found))
511 def get_file(self, dircap, path):
512 body = self.webapi_call('GET', '/uri/%s/%s' % (dircap, path))
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)
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)
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)
525 def _write_test_linear(self, testcap, testdir, name, bs, sz):
526 body = os.urandom(sz)
528 path = os.path.join(testdir, name)
530 except Exception, err:
531 tmpl = 'Could not open file for write at %r: %r'
532 raise TestFailure(tmpl, path, err)
534 for posn in range(0,sz,bs):
535 f.write(body[posn:posn+bs])
537 except Exception, err:
538 tmpl = 'Could not write to file %r: %r'
539 raise TestFailure(tmpl, path, err)
542 self._check_write(testcap, name, body)
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))
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)
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)
556 def _write_test_overlap(self, testcap, testdir, name, bs, sz):
557 body = os.urandom(sz)
559 path = os.path.join(testdir, name)
561 except Exception, err:
562 tmpl = 'Could not open file for write at %r: %r'
563 raise TestFailure(tmpl, path, err)
565 for posn in range(0,sz,bs):
566 start = max(0, posn-bs)
567 end = min(sz, posn+bs)
569 f.write(body[start:end])
571 except Exception, err:
572 tmpl = 'Could not write to file %r: %r'
573 raise TestFailure(tmpl, path, err)
576 self._check_write(testcap, name, body)
579 def test_write_random_scatter(self, testcap, testdir):
581 name = 'random_scatter'
582 body = os.urandom(sz)
585 return min(int(random.paretovariate(.25)), sz/12)
587 # first chop up whole file into random sized chunks
592 slices.append( (posn, body[posn:posn+size]) )
594 random.shuffle(slices) # and randomise their order
597 path = os.path.join(testdir, name)
599 except Exception, err:
600 tmpl = 'Could not open file for write at %r: %r'
601 raise TestFailure(tmpl, path, err)
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:
608 rposn = random.randint(0,sz)
610 f.write(body[rposn:rposn+rsize()])
612 except Exception, err:
613 tmpl = 'Could not write to file %r: %r'
614 raise TestFailure(tmpl, path, err)
617 self._check_write(testcap, name, body)
619 def test_write_partial_overwrite(self, testcap, testdir):
620 name = 'partial_overwrite'
625 def write_file(path, mode, contents, position=None):
628 if position is not None:
632 except Exception, err:
633 tmpl = 'Could not write to file %r: %r'
634 raise TestFailure(tmpl, path, err)
641 except Exception, err:
642 tmpl = 'Could not read file %r: %r'
643 raise TestFailure(tmpl, path, err)
646 path = os.path.join(testdir, name)
647 #write_file(path, 'w', body)
649 cap = self.webapi_call('PUT', '/uri', body)
650 self.attach_node(testcap, cap, name)
653 contents = read_file(path)
655 raise TestFailure('File contents mismatch (%r) %r v.s. %r', path, contents, body)
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)
665 def run_tahoe(self, *args):
666 realargs = ('tahoe',) + args
667 status, output = gather_output(realargs, executable=self.cliexec)
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,
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',
686 def ignore_line(line):
687 for ignorable_line in ignorable_lines:
688 if ignorable_line.match(line):
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)
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)
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))
709 def stop_node(self, basedir):
711 self.run_tahoe('stop', '--basedir', basedir)
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 ')
719 def webapi_call(self, method, path, body=None, **options):
721 path = path + '?' + ('&'.join(['%s=%s' % kv for kv in options.items()]))
723 conn = httplib.HTTPConnection('127.0.0.1', self.port)
724 conn.request(method, path, body = body)
725 resp = conn.getresponse()
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,
739 def create_dirnode(self):
740 return self.webapi_call('PUT', '/uri', t='mkdir').strip()
742 def attach_node(self, dircap, childcap, childname):
743 body = self.webapi_call('PUT',
744 '/uri/%s/%s' % (dircap, childname),
748 assert body.strip() == childcap, `body, dircap, childcap, childname`
750 def polling_operation(self, operation, polldesc, timeout = 10.0, pollinterval = 0.2):
751 totaltime = timeout # Fudging for edge-case SetupFailure description...
753 totalattempts = int(timeout / pollinterval)
755 starttime = time.time()
756 for attempt in range(totalattempts):
757 opstart = time.time()
761 except KeyboardInterrupt, e:
766 totaltime = time.time() - starttime
768 if result is not False:
769 #tmpl = '(Polling took over %.2f seconds.)'
770 #print tmpl % (totaltime,)
773 elif totaltime > timeout:
777 opdelay = time.time() - opstart
778 realinterval = max(0., pollinterval - opdelay)
780 #tmpl = '(Poll attempt %d failed after %.2f seconds, sleeping %.2f seconds.)'
781 #print tmpl % (attempt+1, opdelay, realinterval)
782 time.sleep(realinterval)
785 tmpl = 'Timeout while polling for: %s\n'
786 tmpl += 'Waited %.2f seconds (%d polls).'
787 raise SetupFailure(tmpl, polldesc, totaltime, attempt+1)
789 def warn(self, tmpl, *args):
790 print ('Test Warning: ' + tmpl) % args
793 # SystemTest Exceptions:
794 class Failure (Exception):
795 def __init__(self, tmpl, *args):
796 msg = self.Prefix + (tmpl % args)
797 Exception.__init__(self, msg)
799 class SetupFailure (Failure):
800 Prefix = 'Setup Failure - The test framework encountered an error:\n'
802 class TestFailure (Failure):
803 Prefix = 'TestFailure: '
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')]
815 for input, output in iopairs:
816 result = impl_a.canonicalize_cap(input)
817 self.failUnlessEqual(output, result, 'input == %r' % (input,))
822 class ImplProcessManager(object):
825 def __init__(self, name, module, mount_args, mount_wait, suites, todo=False):
828 self.script = module.__file__
829 self.mount_args = mount_args
830 self.mount_wait = mount_wait
834 def maybe_wait(self, msg='waiting'):
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)
845 print 'Mounting implementation: %s (%s)' % (self.name, self.script)
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,
853 args = ['python', self.script] + [ arg%fields for arg in self.mount_args ]
855 self.maybe_wait('waiting (about to launch fuse)')
858 exitcode, output = gather_output(args)
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)
865 self.proc = subprocess.Popen(args)
868 print 'Unmounting implementation: %s' % (self.name,)
869 args = UNMOUNT_CMD + [self.mountpath]
871 self.maybe_wait('waiting (unmount)')
872 #print os.system('ls -l '+self.mountpath)
873 ec, out = gather_output(args)
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)
882 def gather_output(*args, **kwargs):
884 This expects the child does not require input and that it closes
885 stdout/err eventually.
887 p = subprocess.Popen(stdout = subprocess.PIPE,
888 stderr = subprocess.STDOUT,
891 output = p.stdout.read()
893 return (exitcode, output)
896 def wrap_os_error(meth, *args):
900 raise TestFailure('%s', e)
903 ExpectedCreationOutput = r'(introducer|client) created in (?P<path>.*?)\n'
904 ExpectedStartOutput = r'(.*\n)*STARTING (?P<path>.*?)\n(introducer|client) node probably started'
907 if __name__ == '__main__':
908 sys.exit(main(sys.argv))