]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/test/test_runner.py
Magic Folder.
[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         # creating the node a second time should be rejected
270         rc, out, err = self.run_tahoe(argv)
271         self.failIfEqual(rc, 0, str((out, err, rc)))
272         self.failUnlessEqual(out, "")
273         self.failUnless("is not empty." in err)
274
275         # Fail if there is a non-empty line that doesn't end with a
276         # punctuation mark.
277         for line in err.splitlines():
278             self.failIf(re.search("[\S][^\.!?]$", line), (line,))
279
280         # test that the non --basedir form works too
281         n2 = os.path.join(basedir, command + "-n2")
282         argv = ["--quiet", command, n2]
283         rc, out, err = self.run_tahoe(argv)
284         self.failUnlessEqual(err, "")
285         self.failUnlessEqual(out, "")
286         self.failUnlessEqual(rc, 0)
287         self.failUnless(os.path.exists(n2))
288         self.failUnless(os.path.exists(os.path.join(n2, tac)))
289
290         # test the --node-directory form
291         n3 = os.path.join(basedir, command + "-n3")
292         argv = ["--quiet", "--node-directory", n3, command]
293         rc, out, err = self.run_tahoe(argv)
294         self.failUnlessEqual(err, "")
295         self.failUnlessEqual(out, "")
296         self.failUnlessEqual(rc, 0)
297         self.failUnless(os.path.exists(n3))
298         self.failUnless(os.path.exists(os.path.join(n3, tac)))
299
300         if kind in ("client", "node", "introducer"):
301             # test that the output (without --quiet) includes the base directory
302             n4 = os.path.join(basedir, command + "-n4")
303             argv = [command, n4]
304             rc, out, err = self.run_tahoe(argv)
305             self.failUnlessEqual(err, "")
306             self.failUnlessIn(" created in ", out)
307             self.failUnlessIn(n4, out)
308             self.failIfIn("\\\\?\\", out)
309             self.failUnlessEqual(rc, 0)
310             self.failUnless(os.path.exists(n4))
311             self.failUnless(os.path.exists(os.path.join(n4, tac)))
312
313         # make sure it rejects too many arguments
314         argv = [command, "basedir", "extraarg"]
315         self.failUnlessRaises(usage.UsageError,
316                               runner.runner, argv,
317                               run_by_human=False)
318
319         # when creating a non-client, there is no default for the basedir
320         if not is_client:
321             argv = [command]
322             self.failUnlessRaises(usage.UsageError,
323                                   runner.runner, argv,
324                                   run_by_human=False)
325
326
327     def test_node(self):
328         self.do_create("node")
329
330     def test_client(self):
331         # create-client should behave like create-node --no-storage.
332         self.do_create("client")
333
334     def test_introducer(self):
335         self.do_create("introducer")
336
337     def test_key_generator(self):
338         self.do_create("key-generator")
339
340     def test_stats_gatherer(self):
341         self.do_create("stats-gatherer")
342
343     def test_subcommands(self):
344         # no arguments should trigger a command listing, via UsageError
345         self.failUnlessRaises(usage.UsageError,
346                               runner.runner,
347                               [],
348                               run_by_human=False)
349
350
351 class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
352               RunBinTahoeMixin):
353     # exercise "tahoe start", for both introducer, client node, and
354     # key-generator, by spawning "tahoe start" as a subprocess. This doesn't
355     # get us figleaf-based line-level coverage, but it does a better job of
356     # confirming that the user can actually run "./bin/tahoe start" and
357     # expect it to work. This verifies that bin/tahoe sets up PYTHONPATH and
358     # the like correctly.
359
360     # This doesn't work on cygwin (it hangs forever), so we skip this test
361     # when we're on cygwin. It is likely that "tahoe start" itself doesn't
362     # work on cygwin: twisted seems unable to provide a version of
363     # spawnProcess which really works there.
364
365     def workdir(self, name):
366         basedir = os.path.join("test_runner", "RunNode", name)
367         fileutil.make_dirs(basedir)
368         return basedir
369
370     def test_introducer(self):
371         self.skip_if_cannot_daemonize()
372
373         basedir = self.workdir("test_introducer")
374         c1 = os.path.join(basedir, "c1")
375         exit_trigger_file = os.path.join(c1, Client.EXIT_TRIGGER_FILE)
376         twistd_pid_file = os.path.join(c1, "twistd.pid")
377         introducer_furl_file = os.path.join(c1, "private", "introducer.furl")
378         portnum_file = os.path.join(c1, "introducer.port")
379         node_url_file = os.path.join(c1, "node.url")
380         config_file = os.path.join(c1, "tahoe.cfg")
381
382         d = self.run_bintahoe(["--quiet", "create-introducer", "--basedir", c1])
383         def _cb(res):
384             out, err, rc_or_sig = res
385             self.failUnlessEqual(rc_or_sig, 0)
386
387             # This makes sure that node.url is written, which allows us to
388             # detect when the introducer restarts in _node_has_restarted below.
389             config = fileutil.read(config_file)
390             self.failUnlessIn('\nweb.port = \n', config)
391             fileutil.write(config_file, config.replace('\nweb.port = \n', '\nweb.port = 0\n'))
392
393             # by writing this file, we get ten seconds before the node will
394             # exit. This insures that even if the test fails (and the 'stop'
395             # command doesn't work), the client should still terminate.
396             fileutil.write(exit_trigger_file, "")
397             # now it's safe to start the node
398         d.addCallback(_cb)
399
400         def _then_start_the_node(res):
401             return self.run_bintahoe(["--quiet", "start", c1])
402         d.addCallback(_then_start_the_node)
403
404         def _cb2(res):
405             out, err, rc_or_sig = res
406
407             fileutil.write(exit_trigger_file, "")
408             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
409             self.failUnlessEqual(rc_or_sig, 0, errstr)
410             self.failUnlessEqual(out, "", errstr)
411             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
412
413             # the parent (twistd) has exited. However, twistd writes the pid
414             # from the child, not the parent, so we can't expect twistd.pid
415             # to exist quite yet.
416
417             # the node is running, but it might not have made it past the
418             # first reactor turn yet, and if we kill it too early, it won't
419             # remove the twistd.pid file. So wait until it does something
420             # that we know it won't do until after the first turn.
421         d.addCallback(_cb2)
422
423         def _node_has_started():
424             return os.path.exists(introducer_furl_file)
425         d.addCallback(lambda res: self.poll(_node_has_started))
426
427         def _started(res):
428             # read the introducer.furl and introducer.port files so we can
429             # check that their contents don't change on restart
430             self.furl = fileutil.read(introducer_furl_file)
431             self.failUnless(os.path.exists(portnum_file))
432             self.portnum = fileutil.read(portnum_file)
433
434             fileutil.write(exit_trigger_file, "")
435             self.failUnless(os.path.exists(twistd_pid_file))
436             self.failUnless(os.path.exists(node_url_file))
437
438             # rm this so we can detect when the second incarnation is ready
439             os.unlink(node_url_file)
440             return self.run_bintahoe(["--quiet", "restart", c1])
441         d.addCallback(_started)
442
443         def _then(res):
444             out, err, rc_or_sig = res
445             fileutil.write(exit_trigger_file, "")
446             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
447             self.failUnlessEqual(rc_or_sig, 0, errstr)
448             self.failUnlessEqual(out, "", errstr)
449             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
450         d.addCallback(_then)
451
452         # Again, the second incarnation of the node might not be ready yet,
453         # so poll until it is. This time introducer_furl_file already
454         # exists, so we check for the existence of node_url_file instead.
455         def _node_has_restarted():
456             return os.path.exists(node_url_file) and os.path.exists(portnum_file)
457         d.addCallback(lambda res: self.poll(_node_has_restarted))
458
459         def _check_same_furl_and_port(res):
460             self.failUnless(os.path.exists(introducer_furl_file))
461             self.failUnlessEqual(self.furl, fileutil.read(introducer_furl_file))
462             self.failUnlessEqual(self.portnum, fileutil.read(portnum_file))
463         d.addCallback(_check_same_furl_and_port)
464
465         # Now we can kill it. TODO: On a slow machine, the node might kill
466         # itself before we get a chance to, especially if spawning the
467         # 'tahoe stop' command takes a while.
468         def _stop(res):
469             fileutil.write(exit_trigger_file, "")
470             self.failUnless(os.path.exists(twistd_pid_file))
471
472             return self.run_bintahoe(["--quiet", "stop", c1])
473         d.addCallback(_stop)
474
475         def _after_stopping(res):
476             out, err, rc_or_sig = res
477             fileutil.write(exit_trigger_file, "")
478             # the parent has exited by now
479             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
480             self.failUnlessEqual(rc_or_sig, 0, errstr)
481             self.failUnlessEqual(out, "", errstr)
482             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
483             # the parent was supposed to poll and wait until it sees
484             # twistd.pid go away before it exits, so twistd.pid should be
485             # gone by now.
486             self.failIf(os.path.exists(twistd_pid_file))
487         d.addCallback(_after_stopping)
488         d.addBoth(self._remove, exit_trigger_file)
489         return d
490     # This test has hit a 240-second timeout on our feisty2.5 buildslave, and a 480-second timeout
491     # on Francois's Lenny-armv5tel buildslave.
492     test_introducer.timeout = 960
493
494     def test_client_no_noise(self):
495         self.skip_if_cannot_daemonize()
496
497         basedir = self.workdir("test_client_no_noise")
498         c1 = os.path.join(basedir, "c1")
499         exit_trigger_file = os.path.join(c1, Client.EXIT_TRIGGER_FILE)
500         twistd_pid_file = os.path.join(c1, "twistd.pid")
501         portnum_file = os.path.join(c1, "client.port")
502
503         d = self.run_bintahoe(["--quiet", "create-client", "--basedir", c1, "--webport", "0"])
504         def _cb(res):
505             out, err, rc_or_sig = res
506             errstr = "cc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
507             assert rc_or_sig == 0, errstr
508             self.failUnlessEqual(rc_or_sig, 0)
509
510             # By writing this file, we get two minutes before the client will exit. This ensures
511             # that even if the 'stop' command doesn't work (and the test fails), the client should
512             # still terminate.
513             fileutil.write(exit_trigger_file, "")
514             # now it's safe to start the node
515         d.addCallback(_cb)
516
517         def _start(res):
518             return self.run_bintahoe(["--quiet", "start", c1])
519         d.addCallback(_start)
520
521         def _cb2(res):
522             out, err, rc_or_sig = res
523             errstr = "cc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
524             fileutil.write(exit_trigger_file, "")
525             self.failUnlessEqual(rc_or_sig, 0, errstr)
526             self.failUnlessEqual(out, "", errstr) # If you emit noise, you fail this test.
527             errlines = err.split("\n")
528             self.failIf([True for line in errlines if (line != "" and "UserWarning: Unbuilt egg for setuptools" not in line
529                                                                   and "from pkg_resources import load_entry_point" not in line)], errstr)
530             if err != "":
531                 raise unittest.SkipTest("This test is known not to pass on Ubuntu Lucid; see #1235.")
532
533             # the parent (twistd) has exited. However, twistd writes the pid
534             # from the child, not the parent, so we can't expect twistd.pid
535             # to exist quite yet.
536
537             # the node is running, but it might not have made it past the
538             # first reactor turn yet, and if we kill it too early, it won't
539             # remove the twistd.pid file. So wait until it does something
540             # that we know it won't do until after the first turn.
541         d.addCallback(_cb2)
542
543         def _node_has_started():
544             return os.path.exists(portnum_file)
545         d.addCallback(lambda res: self.poll(_node_has_started))
546
547         # now we can kill it. TODO: On a slow machine, the node might kill
548         # itself before we get a chance to, especially if spawning the
549         # 'tahoe stop' command takes a while.
550         def _stop(res):
551             self.failUnless(os.path.exists(twistd_pid_file),
552                             (twistd_pid_file, os.listdir(os.path.dirname(twistd_pid_file))))
553             return self.run_bintahoe(["--quiet", "stop", c1])
554         d.addCallback(_stop)
555         d.addBoth(self._remove, exit_trigger_file)
556         return d
557
558     def test_client(self):
559         self.skip_if_cannot_daemonize()
560
561         basedir = self.workdir("test_client")
562         c1 = os.path.join(basedir, "c1")
563         exit_trigger_file = os.path.join(c1, Client.EXIT_TRIGGER_FILE)
564         twistd_pid_file = os.path.join(c1, "twistd.pid")
565         portnum_file = os.path.join(c1, "client.port")
566         node_url_file = os.path.join(c1, "node.url")
567         config_file = os.path.join(c1, "tahoe.cfg")
568
569         d = self.run_bintahoe(["--quiet", "create-node", "--basedir", c1, "--webport", "0"])
570         def _cb(res):
571             out, err, rc_or_sig = res
572             self.failUnlessEqual(rc_or_sig, 0)
573
574             # Check that the --webport option worked.
575             config = fileutil.read(config_file)
576             self.failUnlessIn('\nweb.port = 0\n', config)
577
578             # By writing this file, we get two minutes before the client will exit. This ensures
579             # that even if the 'stop' command doesn't work (and the test fails), the client should
580             # still terminate.
581             fileutil.write(exit_trigger_file, "")
582             # now it's safe to start the node
583         d.addCallback(_cb)
584
585         def _start(res):
586             return self.run_bintahoe(["--quiet", "start", c1])
587         d.addCallback(_start)
588
589         def _cb2(res):
590             out, err, rc_or_sig = res
591             fileutil.write(exit_trigger_file, "")
592             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
593             self.failUnlessEqual(rc_or_sig, 0, errstr)
594             self.failUnlessEqual(out, "", errstr)
595             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
596
597             # the parent (twistd) has exited. However, twistd writes the pid
598             # from the child, not the parent, so we can't expect twistd.pid
599             # to exist quite yet.
600
601             # the node is running, but it might not have made it past the
602             # first reactor turn yet, and if we kill it too early, it won't
603             # remove the twistd.pid file. So wait until it does something
604             # that we know it won't do until after the first turn.
605         d.addCallback(_cb2)
606
607         def _node_has_started():
608             # this depends upon both files being created atomically
609             return os.path.exists(node_url_file) and os.path.exists(portnum_file)
610         d.addCallback(lambda res: self.poll(_node_has_started))
611
612         def _started(res):
613             # read the client.port file so we can check that its contents
614             # don't change on restart
615             self.portnum = fileutil.read(portnum_file)
616
617             fileutil.write(exit_trigger_file, "")
618             self.failUnless(os.path.exists(twistd_pid_file))
619
620             # rm this so we can detect when the second incarnation is ready
621             os.unlink(node_url_file)
622             return self.run_bintahoe(["--quiet", "restart", c1])
623         d.addCallback(_started)
624
625         def _cb3(res):
626             out, err, rc_or_sig = res
627
628             fileutil.write(exit_trigger_file, "")
629             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
630             self.failUnlessEqual(rc_or_sig, 0, errstr)
631             self.failUnlessEqual(out, "", errstr)
632             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
633         d.addCallback(_cb3)
634
635         # again, the second incarnation of the node might not be ready yet,
636         # so poll until it is
637         d.addCallback(lambda res: self.poll(_node_has_started))
638
639         def _check_same_port(res):
640             self.failUnlessEqual(self.portnum, fileutil.read(portnum_file))
641         d.addCallback(_check_same_port)
642
643         # now we can kill it. TODO: On a slow machine, the node might kill
644         # itself before we get a chance to, especially if spawning the
645         # 'tahoe stop' command takes a while.
646         def _stop(res):
647             fileutil.write(exit_trigger_file, "")
648             self.failUnless(os.path.exists(twistd_pid_file),
649                             (twistd_pid_file, os.listdir(os.path.dirname(twistd_pid_file))))
650             return self.run_bintahoe(["--quiet", "stop", c1])
651         d.addCallback(_stop)
652
653         def _cb4(res):
654             out, err, rc_or_sig = res
655
656             fileutil.write(exit_trigger_file, "")
657             # the parent has exited by now
658             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
659             self.failUnlessEqual(rc_or_sig, 0, errstr)
660             self.failUnlessEqual(out, "", errstr)
661             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
662             # the parent was supposed to poll and wait until it sees
663             # twistd.pid go away before it exits, so twistd.pid should be
664             # gone by now.
665             self.failIf(os.path.exists(twistd_pid_file))
666         d.addCallback(_cb4)
667         d.addBoth(self._remove, exit_trigger_file)
668         return d
669
670     def _remove(self, res, file):
671         fileutil.remove(file)
672         return res
673
674     def test_baddir(self):
675         self.skip_if_cannot_daemonize()
676         basedir = self.workdir("test_baddir")
677         fileutil.make_dirs(basedir)
678
679         d = self.run_bintahoe(["--quiet", "start", "--basedir", basedir])
680         def _cb(res):
681             out, err, rc_or_sig = res
682             self.failUnlessEqual(rc_or_sig, 1)
683             self.failUnless("is not a recognizable node directory" in err, err)
684         d.addCallback(_cb)
685
686         def _then_stop_it(res):
687             return self.run_bintahoe(["--quiet", "stop", "--basedir", basedir])
688         d.addCallback(_then_stop_it)
689
690         def _cb2(res):
691             out, err, rc_or_sig = res
692             self.failUnlessEqual(rc_or_sig, 2)
693             self.failUnless("does not look like a running node directory" in err)
694         d.addCallback(_cb2)
695
696         def _then_start_in_bogus_basedir(res):
697             not_a_dir = os.path.join(basedir, "bogus")
698             return self.run_bintahoe(["--quiet", "start", "--basedir", not_a_dir])
699         d.addCallback(_then_start_in_bogus_basedir)
700
701         def _cb3(res):
702             out, err, rc_or_sig = res
703             self.failUnlessEqual(rc_or_sig, 1)
704             self.failUnlessIn("does not look like a directory at all", err)
705         d.addCallback(_cb3)
706         return d
707
708     def test_keygen(self):
709         self.skip_if_cannot_daemonize()
710
711         basedir = self.workdir("test_keygen")
712         c1 = os.path.join(basedir, "c1")
713         twistd_pid_file = os.path.join(c1, "twistd.pid")
714         keygen_furl_file = os.path.join(c1, "key_generator.furl")
715
716         d = self.run_bintahoe(["--quiet", "create-key-generator", "--basedir", c1])
717         def _cb(res):
718             out, err, rc_or_sig = res
719             self.failUnlessEqual(rc_or_sig, 0)
720         d.addCallback(_cb)
721
722         def _start(res):
723             return self.run_bintahoe(["--quiet", "start", c1])
724         d.addCallback(_start)
725
726         def _cb2(res):
727             out, err, rc_or_sig = res
728             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
729             self.failUnlessEqual(rc_or_sig, 0, errstr)
730             self.failUnlessEqual(out, "", errstr)
731             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
732
733             # the parent (twistd) has exited. However, twistd writes the pid
734             # from the child, not the parent, so we can't expect twistd.pid
735             # to exist quite yet.
736
737             # the node is running, but it might not have made it past the
738             # first reactor turn yet, and if we kill it too early, it won't
739             # remove the twistd.pid file. So wait until it does something
740             # that we know it won't do until after the first turn.
741         d.addCallback(_cb2)
742
743         def _node_has_started():
744             return os.path.exists(keygen_furl_file)
745         d.addCallback(lambda res: self.poll(_node_has_started))
746
747         def _started(res):
748             self.failUnless(os.path.exists(twistd_pid_file))
749             # rm this so we can detect when the second incarnation is ready
750             os.unlink(keygen_furl_file)
751             return self.run_bintahoe(["--quiet", "restart", c1])
752         d.addCallback(_started)
753
754         def _cb3(res):
755             out, err, rc_or_sig = res
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         d.addCallback(_cb3)
761
762         # again, the second incarnation of the node might not be ready yet,
763         # so poll until it is
764         d.addCallback(lambda res: self.poll(_node_has_started))
765
766         # now we can kill it. TODO: On a slow machine, the node might kill
767         # itself before we get a chance too, especially if spawning the
768         # 'tahoe stop' command takes a while.
769         def _stop(res):
770             self.failUnless(os.path.exists(twistd_pid_file))
771             return self.run_bintahoe(["--quiet", "stop", c1])
772         d.addCallback(_stop)
773
774         def _cb4(res):
775             out, err, rc_or_sig = res
776             # the parent has exited by now
777             errstr = "rc=%d, OUT: '%s', ERR: '%s'" % (rc_or_sig, out, err)
778             self.failUnlessEqual(rc_or_sig, 0, errstr)
779             self.failUnlessEqual(out, "", errstr)
780             # self.failUnlessEqual(err, "", errstr) # See test_client_no_noise -- for now we ignore noise.
781             # the parent was supposed to poll and wait until it sees
782             # twistd.pid go away before it exits, so twistd.pid should be
783             # gone by now.
784             self.failIf(os.path.exists(twistd_pid_file))
785         d.addCallback(_cb4)
786         return d