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