]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_runner.py
a9c162e8caf447970645d5e800443fdd990f5982
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / test / test_runner.py
1 import os.path, re, sys, subprocess
2 from cStringIO import StringIO
3
4 from twisted.trial import unittest
5
6 from twisted.python import usage, runtime
7 from twisted.internet import threads
8
9 from allmydata.util import fileutil, pollmixin
10 from allmydata.util.encodingutil import unicode_to_argv, unicode_to_output, \
11     get_filesystem_encoding
12 from allmydata.scripts import runner
13 from allmydata.client import Client
14 from allmydata.test import common_util
15 import allmydata
16
17
18 timeout = 240
19
20 def get_root_from_file(src):
21     srcdir = os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(src))))
22
23     root = os.path.dirname(srcdir)
24     if os.path.basename(srcdir) == 'site-packages':
25         if re.search(r'python.+\..+', os.path.basename(root)):
26             root = os.path.dirname(root)
27         root = os.path.dirname(root)
28     elif os.path.basename(root) == 'src':
29         root = os.path.dirname(root)
30
31     return root
32
33 srcfile = allmydata.__file__
34 rootdir = get_root_from_file(srcfile)
35
36 if hasattr(sys, 'frozen'):
37     bintahoe = os.path.join(rootdir, 'tahoe')
38     if sys.platform == "win32" and os.path.exists(bintahoe + '.exe'):
39         bintahoe += '.exe'
40 else:
41     bintahoe = os.path.join(rootdir, 'bin', 'tahoe')
42     if sys.platform == "win32":
43         bintahoe += '.pyscript'
44         if not os.path.exists(bintahoe):
45             alt_bintahoe = os.path.join(rootdir, 'Scripts', 'tahoe.pyscript')
46             if os.path.exists(alt_bintahoe):
47                 bintahoe = alt_bintahoe
48
49
50 class RunBinTahoeMixin:
51     def skip_if_cannot_run_bintahoe(self):
52         if not os.path.exists(bintahoe):
53             raise unittest.SkipTest("The bin/tahoe script isn't to be found in the expected location (%s), and I don't want to test a 'tahoe' executable that I find somewhere else, in case it isn't the right executable for this version of Tahoe. Perhaps running 'setup.py build' again will help." % (bintahoe,))
54
55     def skip_if_cannot_daemonize(self):
56         self.skip_if_cannot_run_bintahoe()
57         if runtime.platformType == "win32":
58             # twistd on windows doesn't daemonize. cygwin should work normally.
59             raise unittest.SkipTest("twistd does not fork under windows")
60
61     def run_bintahoe(self, args, stdin=None, python_options=[], env=None):
62         self.skip_if_cannot_run_bintahoe()
63
64         if hasattr(sys, 'frozen'):
65             if python_options:
66                 raise unittest.SkipTest("This test doesn't apply to frozen builds.")
67             command = [bintahoe] + args
68         else:
69             command = [sys.executable] + python_options + [bintahoe] + args
70
71         if stdin is None:
72             stdin_stream = None
73         else:
74             stdin_stream = subprocess.PIPE
75
76         def _run():
77             p = subprocess.Popen(command, stdin=stdin_stream, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
78             (out, err) = p.communicate(stdin)
79             return (out, err, p.returncode)
80         return threads.deferToThread(_run)
81
82
83 class BinTahoe(common_util.SignalMixin, unittest.TestCase, RunBinTahoeMixin):
84     def _check_right_code(self, file_to_check):
85         root_to_check = get_root_from_file(file_to_check)
86         if os.path.basename(root_to_check) == 'dist':
87             root_to_check = os.path.dirname(root_to_check)
88
89         cwd = os.path.normcase(os.path.realpath("."))
90         root_from_cwd = os.path.dirname(cwd)
91         if os.path.basename(root_from_cwd) == 'src':
92             root_from_cwd = os.path.dirname(root_from_cwd)
93
94         # This is needed if we are running in a temporary directory created by 'make tmpfstest'.
95         if os.path.basename(root_from_cwd).startswith('tmp'):
96             root_from_cwd = os.path.dirname(root_from_cwd)
97
98         same = (root_from_cwd == root_to_check)
99         if not same:
100             try:
101                 same = os.path.samefile(root_from_cwd, root_to_check)
102             except AttributeError, e:
103                 e  # hush pyflakes
104
105         if not same:
106             msg = ("We seem to be testing the code at %r,\n"
107                    "(according to the source filename %r),\n"
108                    "but expected to be testing the code at %r.\n"
109                    % (root_to_check, file_to_check, root_from_cwd))
110
111             root_from_cwdu = os.path.dirname(os.path.normcase(os.path.normpath(os.getcwdu())))
112             if os.path.basename(root_from_cwdu) == u'src':
113                 root_from_cwdu = os.path.dirname(root_from_cwdu)
114
115             # This is needed if we are running in a temporary directory created by 'make tmpfstest'.
116             if os.path.basename(root_from_cwdu).startswith(u'tmp'):
117                 root_from_cwdu = os.path.dirname(root_from_cwdu)
118
119             if not isinstance(root_from_cwd, unicode) and root_from_cwd.decode(get_filesystem_encoding(), 'replace') != root_from_cwdu:
120                 msg += ("However, this may be a false alarm because the current directory path\n"
121                         "is not representable in the filesystem encoding. Please run the tests\n"
122                         "from the root of the Tahoe-LAFS distribution at a non-Unicode path.")
123                 raise unittest.SkipTest(msg)
124             else:
125                 msg += "Please run the tests from the root of the Tahoe-LAFS distribution."
126                 self.fail(msg)
127
128     def test_the_right_code(self):
129         self._check_right_code(srcfile)
130
131     def test_import_in_repl(self):
132         d = self.run_bintahoe(["debug", "repl"],
133                               stdin="import allmydata; print; print allmydata.__file__")
134         def _cb(res):
135             out, err, rc_or_sig = res
136             self.failUnlessEqual(rc_or_sig, 0, str(res))
137             lines = out.splitlines()
138             self.failUnlessIn('>>>', lines[0], str(res))
139             self._check_right_code(lines[1])
140         d.addCallback(_cb)
141         return d
142     # The timeout was exceeded on FreeStorm's CentOS5-i386.
143     test_import_in_repl.timeout = 480
144
145     def test_path(self):
146         d = self.run_bintahoe(["--version-and-path"])
147         def _cb(res):
148             from allmydata import normalized_version
149
150             out, err, rc_or_sig = res
151             self.failUnlessEqual(rc_or_sig, 0, str(res))
152
153             # Fail unless the __appname__ package is *this* version *and*
154             # was loaded from *this* source directory.
155
156             required_verstr = str(allmydata.__version__)
157
158             self.failIfEqual(required_verstr, "unknown",
159                              "We don't know our version, because this distribution didn't come "
160                              "with a _version.py and 'setup.py update_version' hasn't been run.")
161
162             srcdir = os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile))))
163             info = repr((res, allmydata.__appname__, required_verstr, srcdir))
164
165             appverpath = out.split(')')[0]
166             (appverfull, path) = appverpath.split('] (')
167             (appver, comment) = appverfull.split(' [')
168             (branch, full_version) = comment.split(': ')
169             (app, ver) = appver.split(': ')
170
171             self.failUnlessEqual(app, allmydata.__appname__, info)
172             norm_ver = normalized_version(ver)
173             norm_required = normalized_version(required_verstr)
174             self.failUnlessEqual(norm_ver, norm_required, info)
175             self.failUnlessEqual(path, srcdir, info)
176             self.failUnlessEqual(branch, allmydata.branch)
177             self.failUnlessEqual(full_version, allmydata.full_version)
178         d.addCallback(_cb)
179         return d
180
181     def test_unicode_arguments_and_output(self):
182         self.skip_if_cannot_run_bintahoe()
183
184         tricky = u"\u2621"
185         try:
186             tricky_arg = unicode_to_argv(tricky, mangle=True)
187             tricky_out = unicode_to_output(tricky)
188         except UnicodeEncodeError:
189             raise unittest.SkipTest("A non-ASCII argument/output could not be encoded on this platform.")
190
191         d = self.run_bintahoe([tricky_arg])
192         def _cb(res):
193             out, err, rc_or_sig = res
194             self.failUnlessEqual(rc_or_sig, 1, str(res))
195             self.failUnlessIn("Unknown command: "+tricky_out, out)
196         d.addCallback(_cb)
197         return d
198
199     def test_run_with_python_options(self):
200         # -t is a harmless option that warns about tabs.
201         d = self.run_bintahoe(["--version"], python_options=["-t"])
202         def _cb(res):
203             out, err, rc_or_sig = res
204             self.failUnlessEqual(rc_or_sig, 0, str(res))
205             self.failUnless(out.startswith(allmydata.__appname__+':'), str(res))
206         d.addCallback(_cb)
207         return d
208
209     def test_version_no_noise(self):
210         self.skip_if_cannot_run_bintahoe()
211
212         d = self.run_bintahoe(["--version"])
213         def _cb(res):
214             out, err, rc_or_sig = res
215             self.failUnlessEqual(rc_or_sig, 0, str(res))
216             self.failUnless(out.startswith(allmydata.__appname__+':'), str(res))
217             self.failIfIn("DeprecationWarning", out, str(res))
218             errlines = err.split("\n")
219             self.failIf([True for line in errlines if (line != "" and "UserWarning: Unbuilt egg for setuptools" not in line
220                                                                   and "from pkg_resources import load_entry_point" not in line)], str(res))
221             if err != "":
222                 raise unittest.SkipTest("This test is known not to pass on Ubuntu Lucid; see #1235.")
223         d.addCallback(_cb)
224         return d
225
226
227 class CreateNode(unittest.TestCase):
228     # exercise "tahoe create-node", create-introducer,
229     # create-key-generator, and create-stats-gatherer, by calling the
230     # corresponding code as a subroutine.
231
232     def workdir(self, name):
233         basedir = os.path.join("test_runner", "CreateNode", name)
234         fileutil.make_dirs(basedir)
235         return basedir
236
237     def run_tahoe(self, argv):
238         out,err = StringIO(), StringIO()
239         rc = runner.runner(argv, stdout=out, stderr=err)
240         return rc, out.getvalue(), err.getvalue()
241
242     def do_create(self, kind):
243         basedir = self.workdir("test_" + kind)
244         command = "create-" + kind
245         is_client = kind in ("node", "client")
246         tac = is_client and "tahoe-client.tac" or ("tahoe-" + kind + ".tac")
247
248         n1 = os.path.join(basedir, command + "-n1")
249         argv = ["--quiet", command, "--basedir", n1]
250         rc, out, err = self.run_tahoe(argv)
251         self.failUnlessEqual(err, "")
252         self.failUnlessEqual(out, "")
253         self.failUnlessEqual(rc, 0)
254         self.failUnless(os.path.exists(n1))
255         self.failUnless(os.path.exists(os.path.join(n1, tac)))
256
257         if is_client:
258             # tahoe.cfg should exist, and should have storage enabled for
259             # 'create-node', and disabled for 'create-client'.
260             tahoe_cfg = os.path.join(n1, "tahoe.cfg")
261             self.failUnless(os.path.exists(tahoe_cfg))
262             content = fileutil.read(tahoe_cfg).replace('\r\n', '\n')
263             if kind == "client":
264                 self.failUnless(re.search(r"\n\[storage\]\n#.*\nenabled = false\n", content), content)
265             else:
266                 self.failUnless(re.search(r"\n\[storage\]\n#.*\nenabled = true\n", content), content)
267                 self.failUnless("\nreserved_space = 1G\n" in content)
268
269             self.failUnless(re.search(r"\n\[magic_folder\]\n#.*\n#enabled = false\n", content), content)
270
271         # creating the node a second time should be rejected
272         rc, out, err = self.run_tahoe(argv)
273         self.failIfEqual(rc, 0, str((out, err, rc)))
274         self.failUnlessEqual(out, "")
275         self.failUnless("is not empty." in err)
276
277         # Fail if there is a non-empty line that doesn't end with a
278         # punctuation mark.
279         for line in err.splitlines():
280             self.failIf(re.search("[\S][^\.!?]$", line), (line,))
281
282         # test that the non --basedir form works too
283         n2 = os.path.join(basedir, command + "-n2")
284         argv = ["--quiet", command, n2]
285         rc, out, err = self.run_tahoe(argv)
286         self.failUnlessEqual(err, "")
287         self.failUnlessEqual(out, "")
288         self.failUnlessEqual(rc, 0)
289         self.failUnless(os.path.exists(n2))
290         self.failUnless(os.path.exists(os.path.join(n2, tac)))
291
292         # test the --node-directory form
293         n3 = os.path.join(basedir, command + "-n3")
294         argv = ["--quiet", "--node-directory", n3, command]
295         rc, out, err = self.run_tahoe(argv)
296         self.failUnlessEqual(err, "")
297         self.failUnlessEqual(out, "")
298         self.failUnlessEqual(rc, 0)
299         self.failUnless(os.path.exists(n3))
300         self.failUnless(os.path.exists(os.path.join(n3, tac)))
301
302         if kind in ("client", "node", "introducer"):
303             # test that the output (without --quiet) includes the base directory
304             n4 = os.path.join(basedir, command + "-n4")
305             argv = [command, n4]
306             rc, out, err = self.run_tahoe(argv)
307             self.failUnlessEqual(err, "")
308             self.failUnlessIn(" created in ", out)
309             self.failUnlessIn(n4, out)
310             self.failIfIn("\\\\?\\", out)
311             self.failUnlessEqual(rc, 0)
312             self.failUnless(os.path.exists(n4))
313             self.failUnless(os.path.exists(os.path.join(n4, tac)))
314
315         # make sure it rejects too many arguments
316         argv = [command, "basedir", "extraarg"]
317         self.failUnlessRaises(usage.UsageError,
318                               runner.runner, argv,
319                               run_by_human=False)
320
321         # when creating a non-client, there is no default for the basedir
322         if not is_client:
323             argv = [command]
324             self.failUnlessRaises(usage.UsageError,
325                                   runner.runner, argv,
326                                   run_by_human=False)
327
328
329     def test_node(self):
330         self.do_create("node")
331
332     def test_client(self):
333         # create-client should behave like create-node --no-storage.
334         self.do_create("client")
335
336     def test_introducer(self):
337         self.do_create("introducer")
338
339     def test_key_generator(self):
340         self.do_create("key-generator")
341
342     def test_stats_gatherer(self):
343         self.do_create("stats-gatherer")
344
345     def test_subcommands(self):
346         # no arguments should trigger a command listing, via UsageError
347         self.failUnlessRaises(usage.UsageError,
348                               runner.runner,
349                               [],
350                               run_by_human=False)
351
352
353 class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
354               RunBinTahoeMixin):
355     # exercise "tahoe start", for both introducer, client node, and
356     # key-generator, by spawning "tahoe start" as a subprocess. This doesn't
357     # get us figleaf-based line-level coverage, but it does a better job of
358     # confirming that the user can actually run "./bin/tahoe start" and
359     # expect it to work. This verifies that bin/tahoe sets up PYTHONPATH and
360     # the like correctly.
361
362     # This doesn't work on cygwin (it hangs forever), so we skip this test
363     # when we're on cygwin. It is likely that "tahoe start" itself doesn't
364     # work on cygwin: twisted seems unable to provide a version of
365     # spawnProcess which really works there.
366
367     def workdir(self, name):
368         basedir = os.path.join("test_runner", "RunNode", name)
369         fileutil.make_dirs(basedir)
370         return basedir
371
372     def test_introducer(self):
373         self.skip_if_cannot_daemonize()
374
375         basedir = self.workdir("test_introducer")
376         c1 = os.path.join(basedir, "c1")
377         exit_trigger_file = os.path.join(c1, Client.EXIT_TRIGGER_FILE)
378         twistd_pid_file = os.path.join(c1, "twistd.pid")
379         introducer_furl_file = os.path.join(c1, "private", "introducer.furl")
380         portnum_file = os.path.join(c1, "introducer.port")
381         node_url_file = os.path.join(c1, "node.url")
382         config_file = os.path.join(c1, "tahoe.cfg")
383
384         d = self.run_bintahoe(["--quiet", "create-introducer", "--basedir", c1])
385         def _cb(res):
386             out, err, rc_or_sig = res
387             self.failUnlessEqual(rc_or_sig, 0)
388
389             # This makes sure that node.url is written, which allows us to
390             # detect when the introducer restarts in _node_has_restarted below.
391             config = fileutil.read(config_file)
392             self.failUnlessIn('\nweb.port = \n', config)
393             fileutil.write(config_file, config.replace('\nweb.port = \n', '\nweb.port = 0\n'))
394
395             # by writing this file, we get ten seconds before the node will
396             # exit. This insures that even if the test fails (and the 'stop'
397             # command doesn't work), the client should still terminate.
398             fileutil.write(exit_trigger_file, "")
399             # now it's safe to start the node
400         d.addCallback(_cb)
401
402         def _then_start_the_node(res):
403             return self.run_bintahoe(["--quiet", "start", c1])
404         d.addCallback(_then_start_the_node)
405
406         def _cb2(res):
407             out, err, rc_or_sig = res
408
409             fileutil.write(exit_trigger_file, "")
410             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
411             self.failUnlessEqual(rc_or_sig, 0, errstr)
412             self.failUnlessEqual(out, "", errstr)
413             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
414
415             # the parent (twistd) has exited. However, twistd writes the pid
416             # from the child, not the parent, so we can't expect twistd.pid
417             # to exist quite yet.
418
419             # the node is running, but it might not have made it past the
420             # first reactor turn yet, and if we kill it too early, it won't
421             # remove the twistd.pid file. So wait until it does something
422             # that we know it won't do until after the first turn.
423         d.addCallback(_cb2)
424
425         def _node_has_started():
426             return os.path.exists(introducer_furl_file)
427         d.addCallback(lambda res: self.poll(_node_has_started))
428
429         def _started(res):
430             # read the introducer.furl and introducer.port files so we can
431             # check that their contents don't change on restart
432             self.furl = fileutil.read(introducer_furl_file)
433             self.failUnless(os.path.exists(portnum_file))
434             self.portnum = fileutil.read(portnum_file)
435
436             fileutil.write(exit_trigger_file, "")
437             self.failUnless(os.path.exists(twistd_pid_file))
438             self.failUnless(os.path.exists(node_url_file))
439
440             # rm this so we can detect when the second incarnation is ready
441             os.unlink(node_url_file)
442             return self.run_bintahoe(["--quiet", "restart", c1])
443         d.addCallback(_started)
444
445         def _then(res):
446             out, err, rc_or_sig = res
447             fileutil.write(exit_trigger_file, "")
448             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
449             self.failUnlessEqual(rc_or_sig, 0, errstr)
450             self.failUnlessEqual(out, "", errstr)
451             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
452         d.addCallback(_then)
453
454         # Again, the second incarnation of the node might not be ready yet,
455         # so poll until it is. This time introducer_furl_file already
456         # exists, so we check for the existence of node_url_file instead.
457         def _node_has_restarted():
458             return os.path.exists(node_url_file) and os.path.exists(portnum_file)
459         d.addCallback(lambda res: self.poll(_node_has_restarted))
460
461         def _check_same_furl_and_port(res):
462             self.failUnless(os.path.exists(introducer_furl_file))
463             self.failUnlessEqual(self.furl, fileutil.read(introducer_furl_file))
464             self.failUnlessEqual(self.portnum, fileutil.read(portnum_file))
465         d.addCallback(_check_same_furl_and_port)
466
467         # Now we can kill it. TODO: On a slow machine, the node might kill
468         # itself before we get a chance to, especially if spawning the
469         # 'tahoe stop' command takes a while.
470         def _stop(res):
471             fileutil.write(exit_trigger_file, "")
472             self.failUnless(os.path.exists(twistd_pid_file))
473
474             return self.run_bintahoe(["--quiet", "stop", c1])
475         d.addCallback(_stop)
476
477         def _after_stopping(res):
478             out, err, rc_or_sig = res
479             fileutil.write(exit_trigger_file, "")
480             # the parent has exited by now
481             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
482             self.failUnlessEqual(rc_or_sig, 0, errstr)
483             self.failUnlessEqual(out, "", errstr)
484             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
485             # the parent was supposed to poll and wait until it sees
486             # twistd.pid go away before it exits, so twistd.pid should be
487             # gone by now.
488             self.failIf(os.path.exists(twistd_pid_file))
489         d.addCallback(_after_stopping)
490         d.addBoth(self._remove, exit_trigger_file)
491         return d
492     # This test has hit a 240-second timeout on our feisty2.5 buildslave, and a 480-second timeout
493     # on Francois's Lenny-armv5tel buildslave.
494     test_introducer.timeout = 960
495
496     def test_client_no_noise(self):
497         self.skip_if_cannot_daemonize()
498
499         basedir = self.workdir("test_client_no_noise")
500         c1 = os.path.join(basedir, "c1")
501         exit_trigger_file = os.path.join(c1, Client.EXIT_TRIGGER_FILE)
502         twistd_pid_file = os.path.join(c1, "twistd.pid")
503         portnum_file = os.path.join(c1, "client.port")
504
505         d = self.run_bintahoe(["--quiet", "create-client", "--basedir", c1, "--webport", "0"])
506         def _cb(res):
507             out, err, rc_or_sig = res
508             errstr = "cc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
509             assert rc_or_sig == 0, errstr
510             self.failUnlessEqual(rc_or_sig, 0)
511
512             # By writing this file, we get two minutes before the client will exit. This ensures
513             # that even if the 'stop' command doesn't work (and the test fails), the client should
514             # still terminate.
515             fileutil.write(exit_trigger_file, "")
516             # now it's safe to start the node
517         d.addCallback(_cb)
518
519         def _start(res):
520             return self.run_bintahoe(["--quiet", "start", c1])
521         d.addCallback(_start)
522
523         def _cb2(res):
524             out, err, rc_or_sig = res
525             errstr = "cc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
526             fileutil.write(exit_trigger_file, "")
527             self.failUnlessEqual(rc_or_sig, 0, errstr)
528             self.failUnlessEqual(out, "", errstr) # If you emit noise, you fail this test.
529             errlines = err.split("\n")
530             self.failIf([True for line in errlines if (line != "" and "UserWarning: Unbuilt egg for setuptools" not in line
531                                                                   and "from pkg_resources import load_entry_point" not in line)], errstr)
532             if err != "":
533                 raise unittest.SkipTest("This test is known not to pass on Ubuntu Lucid; see #1235.")
534
535             # the parent (twistd) has exited. However, twistd writes the pid
536             # from the child, not the parent, so we can't expect twistd.pid
537             # to exist quite yet.
538
539             # the node is running, but it might not have made it past the
540             # first reactor turn yet, and if we kill it too early, it won't
541             # remove the twistd.pid file. So wait until it does something
542             # that we know it won't do until after the first turn.
543         d.addCallback(_cb2)
544
545         def _node_has_started():
546             return os.path.exists(portnum_file)
547         d.addCallback(lambda res: self.poll(_node_has_started))
548
549         # now we can kill it. TODO: On a slow machine, the node might kill
550         # itself before we get a chance to, especially if spawning the
551         # 'tahoe stop' command takes a while.
552         def _stop(res):
553             self.failUnless(os.path.exists(twistd_pid_file),
554                             (twistd_pid_file, os.listdir(os.path.dirname(twistd_pid_file))))
555             return self.run_bintahoe(["--quiet", "stop", c1])
556         d.addCallback(_stop)
557         d.addBoth(self._remove, exit_trigger_file)
558         return d
559
560     def test_client(self):
561         self.skip_if_cannot_daemonize()
562
563         basedir = self.workdir("test_client")
564         c1 = os.path.join(basedir, "c1")
565         exit_trigger_file = os.path.join(c1, Client.EXIT_TRIGGER_FILE)
566         twistd_pid_file = os.path.join(c1, "twistd.pid")
567         portnum_file = os.path.join(c1, "client.port")
568         node_url_file = os.path.join(c1, "node.url")
569         config_file = os.path.join(c1, "tahoe.cfg")
570
571         d = self.run_bintahoe(["--quiet", "create-node", "--basedir", c1, "--webport", "0"])
572         def _cb(res):
573             out, err, rc_or_sig = res
574             self.failUnlessEqual(rc_or_sig, 0)
575
576             # Check that the --webport option worked.
577             config = fileutil.read(config_file)
578             self.failUnlessIn('\nweb.port = 0\n', config)
579
580             # By writing this file, we get two minutes before the client will exit. This ensures
581             # that even if the 'stop' command doesn't work (and the test fails), the client should
582             # still terminate.
583             fileutil.write(exit_trigger_file, "")
584             # now it's safe to start the node
585         d.addCallback(_cb)
586
587         def _start(res):
588             return self.run_bintahoe(["--quiet", "start", c1])
589         d.addCallback(_start)
590
591         def _cb2(res):
592             out, err, rc_or_sig = res
593             fileutil.write(exit_trigger_file, "")
594             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
595             self.failUnlessEqual(rc_or_sig, 0, errstr)
596             self.failUnlessEqual(out, "", errstr)
597             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
598
599             # the parent (twistd) has exited. However, twistd writes the pid
600             # from the child, not the parent, so we can't expect twistd.pid
601             # to exist quite yet.
602
603             # the node is running, but it might not have made it past the
604             # first reactor turn yet, and if we kill it too early, it won't
605             # remove the twistd.pid file. So wait until it does something
606             # that we know it won't do until after the first turn.
607         d.addCallback(_cb2)
608
609         def _node_has_started():
610             # this depends upon both files being created atomically
611             return os.path.exists(node_url_file) and os.path.exists(portnum_file)
612         d.addCallback(lambda res: self.poll(_node_has_started))
613
614         def _started(res):
615             # read the client.port file so we can check that its contents
616             # don't change on restart
617             self.portnum = fileutil.read(portnum_file)
618
619             fileutil.write(exit_trigger_file, "")
620             self.failUnless(os.path.exists(twistd_pid_file))
621
622             # rm this so we can detect when the second incarnation is ready
623             os.unlink(node_url_file)
624             return self.run_bintahoe(["--quiet", "restart", c1])
625         d.addCallback(_started)
626
627         def _cb3(res):
628             out, err, rc_or_sig = res
629
630             fileutil.write(exit_trigger_file, "")
631             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
632             self.failUnlessEqual(rc_or_sig, 0, errstr)
633             self.failUnlessEqual(out, "", errstr)
634             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
635         d.addCallback(_cb3)
636
637         # again, the second incarnation of the node might not be ready yet,
638         # so poll until it is
639         d.addCallback(lambda res: self.poll(_node_has_started))
640
641         def _check_same_port(res):
642             self.failUnlessEqual(self.portnum, fileutil.read(portnum_file))
643         d.addCallback(_check_same_port)
644
645         # now we can kill it. TODO: On a slow machine, the node might kill
646         # itself before we get a chance to, especially if spawning the
647         # 'tahoe stop' command takes a while.
648         def _stop(res):
649             fileutil.write(exit_trigger_file, "")
650             self.failUnless(os.path.exists(twistd_pid_file),
651                             (twistd_pid_file, os.listdir(os.path.dirname(twistd_pid_file))))
652             return self.run_bintahoe(["--quiet", "stop", c1])
653         d.addCallback(_stop)
654
655         def _cb4(res):
656             out, err, rc_or_sig = res
657
658             fileutil.write(exit_trigger_file, "")
659             # the parent has exited by now
660             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
661             self.failUnlessEqual(rc_or_sig, 0, errstr)
662             self.failUnlessEqual(out, "", errstr)
663             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
664             # the parent was supposed to poll and wait until it sees
665             # twistd.pid go away before it exits, so twistd.pid should be
666             # gone by now.
667             self.failIf(os.path.exists(twistd_pid_file))
668         d.addCallback(_cb4)
669         d.addBoth(self._remove, exit_trigger_file)
670         return d
671
672     def _remove(self, res, file):
673         fileutil.remove(file)
674         return res
675
676     def test_baddir(self):
677         self.skip_if_cannot_daemonize()
678         basedir = self.workdir("test_baddir")
679         fileutil.make_dirs(basedir)
680
681         d = self.run_bintahoe(["--quiet", "start", "--basedir", basedir])
682         def _cb(res):
683             out, err, rc_or_sig = res
684             self.failUnlessEqual(rc_or_sig, 1)
685             self.failUnless("is not a recognizable node directory" in err, err)
686         d.addCallback(_cb)
687
688         def _then_stop_it(res):
689             return self.run_bintahoe(["--quiet", "stop", "--basedir", basedir])
690         d.addCallback(_then_stop_it)
691
692         def _cb2(res):
693             out, err, rc_or_sig = res
694             self.failUnlessEqual(rc_or_sig, 2)
695             self.failUnless("does not look like a running node directory" in err)
696         d.addCallback(_cb2)
697
698         def _then_start_in_bogus_basedir(res):
699             not_a_dir = os.path.join(basedir, "bogus")
700             return self.run_bintahoe(["--quiet", "start", "--basedir", not_a_dir])
701         d.addCallback(_then_start_in_bogus_basedir)
702
703         def _cb3(res):
704             out, err, rc_or_sig = res
705             self.failUnlessEqual(rc_or_sig, 1)
706             self.failUnlessIn("does not look like a directory at all", err)
707         d.addCallback(_cb3)
708         return d
709
710     def test_keygen(self):
711         self.skip_if_cannot_daemonize()
712
713         basedir = self.workdir("test_keygen")
714         c1 = os.path.join(basedir, "c1")
715         twistd_pid_file = os.path.join(c1, "twistd.pid")
716         keygen_furl_file = os.path.join(c1, "key_generator.furl")
717
718         d = self.run_bintahoe(["--quiet", "create-key-generator", "--basedir", c1])
719         def _cb(res):
720             out, err, rc_or_sig = res
721             self.failUnlessEqual(rc_or_sig, 0)
722         d.addCallback(_cb)
723
724         def _start(res):
725             return self.run_bintahoe(["--quiet", "start", c1])
726         d.addCallback(_start)
727
728         def _cb2(res):
729             out, err, rc_or_sig = res
730             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
731             self.failUnlessEqual(rc_or_sig, 0, errstr)
732             self.failUnlessEqual(out, "", errstr)
733             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
734
735             # the parent (twistd) has exited. However, twistd writes the pid
736             # from the child, not the parent, so we can't expect twistd.pid
737             # to exist quite yet.
738
739             # the node is running, but it might not have made it past the
740             # first reactor turn yet, and if we kill it too early, it won't
741             # remove the twistd.pid file. So wait until it does something
742             # that we know it won't do until after the first turn.
743         d.addCallback(_cb2)
744
745         def _node_has_started():
746             return os.path.exists(keygen_furl_file)
747         d.addCallback(lambda res: self.poll(_node_has_started))
748
749         def _started(res):
750             self.failUnless(os.path.exists(twistd_pid_file))
751             # rm this so we can detect when the second incarnation is ready
752             os.unlink(keygen_furl_file)
753             return self.run_bintahoe(["--quiet", "restart", c1])
754         d.addCallback(_started)
755
756         def _cb3(res):
757             out, err, rc_or_sig = res
758             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
759             self.failUnlessEqual(rc_or_sig, 0, errstr)
760             self.failUnlessEqual(out, "", errstr)
761             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
762         d.addCallback(_cb3)
763
764         # again, the second incarnation of the node might not be ready yet,
765         # so poll until it is
766         d.addCallback(lambda res: self.poll(_node_has_started))
767
768         # now we can kill it. TODO: On a slow machine, the node might kill
769         # itself before we get a chance too, especially if spawning the
770         # 'tahoe stop' command takes a while.
771         def _stop(res):
772             self.failUnless(os.path.exists(twistd_pid_file))
773             return self.run_bintahoe(["--quiet", "stop", c1])
774         d.addCallback(_stop)
775
776         def _cb4(res):
777             out, err, rc_or_sig = res
778             # the parent has exited by now
779             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
780             self.failUnlessEqual(rc_or_sig, 0, errstr)
781             self.failUnlessEqual(out, "", errstr)
782             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
783             # the parent was supposed to poll and wait until it sees
784             # twistd.pid go away before it exits, so twistd.pid should be
785             # gone by now.
786             self.failIf(os.path.exists(twistd_pid_file))
787         d.addCallback(_cb4)
788         return d