mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-01-19 03:06:33 +00:00
rewrite test_baddir as several tahoe run
-using tests
This commit is contained in:
parent
57fc078383
commit
0e8472c017
@ -3,16 +3,19 @@ __all__ = [
|
||||
"CLINodeAPI",
|
||||
"Expect",
|
||||
"on_stdout",
|
||||
"on_stdout_and_stderr",
|
||||
"wait_for_exit",
|
||||
]
|
||||
|
||||
import os
|
||||
import sys
|
||||
from errno import ENOENT
|
||||
|
||||
import attr
|
||||
|
||||
from twisted.internet.error import (
|
||||
ProcessDone,
|
||||
ProcessTerminated,
|
||||
)
|
||||
from twisted.python.filepath import (
|
||||
FilePath,
|
||||
@ -37,6 +40,9 @@ class Expect(Protocol):
|
||||
def __init__(self):
|
||||
self._expectations = []
|
||||
|
||||
def get_buffered_output(self):
|
||||
return self._buffer
|
||||
|
||||
def expect(self, expectation):
|
||||
if expectation in self._buffer:
|
||||
return succeed(None)
|
||||
@ -55,23 +61,32 @@ class Expect(Protocol):
|
||||
del self._expectations[i]
|
||||
d.callback(None)
|
||||
|
||||
def connectionLost(self, reason):
|
||||
for ignored, d in self._expectations:
|
||||
d.errback(reason)
|
||||
|
||||
class _Stdout(ProcessProtocol):
|
||||
def __init__(self, stdout_protocol):
|
||||
|
||||
class _ProcessProtocolAdapter(ProcessProtocol):
|
||||
def __init__(self, stdout_protocol, fds):
|
||||
self._stdout_protocol = stdout_protocol
|
||||
self._fds = fds
|
||||
|
||||
def connectionMade(self):
|
||||
self._stdout_protocol.makeConnection(self.transport)
|
||||
|
||||
def outReceived(self, data):
|
||||
self._stdout_protocol.dataReceived(data)
|
||||
def childDataReceived(self, childFD, data):
|
||||
if childFD in self._fds:
|
||||
self._stdout_protocol.dataReceived(data)
|
||||
|
||||
def processEnded(self, reason):
|
||||
self._stdout_protocol.connectionLost(reason)
|
||||
|
||||
|
||||
def on_stdout(protocol):
|
||||
return _Stdout(protocol)
|
||||
return _ProcessProtocolAdapter(protocol, {1})
|
||||
|
||||
def on_stdout_and_stderr(protocol):
|
||||
return _ProcessProtocolAdapter(protocol, {1, 2})
|
||||
|
||||
|
||||
@attr.s
|
||||
@ -125,7 +140,11 @@ class CLINodeAPI(object):
|
||||
[u"run", self.basedir.asTextMode().path],
|
||||
)
|
||||
# Don't let the process run away forever.
|
||||
self.active()
|
||||
try:
|
||||
self.active()
|
||||
except OSError as e:
|
||||
if ENOENT != e.errno:
|
||||
raise
|
||||
|
||||
def stop(self, protocol):
|
||||
self._execute(
|
||||
@ -133,24 +152,27 @@ class CLINodeAPI(object):
|
||||
[u"stop", self.basedir.asTextMode().path],
|
||||
)
|
||||
|
||||
def stop_and_wait(self):
|
||||
protocol, ended = wait_for_exit()
|
||||
self.stop(protocol)
|
||||
return ended
|
||||
|
||||
def active(self):
|
||||
# By writing this file, we get two minutes before the client will
|
||||
# exit. This ensures that even if the 'stop' command doesn't work (and
|
||||
# the test fails), the client should still terminate.
|
||||
self.exit_trigger_file.touch()
|
||||
|
||||
def _check_cleanup_reason(self, reason):
|
||||
# Let it fail because the process has already exited.
|
||||
reason.trap(ProcessTerminated)
|
||||
if reason.value.exitCode != COULD_NOT_STOP:
|
||||
return reason
|
||||
return None
|
||||
|
||||
def cleanup(self):
|
||||
stopping = stop_and_wait(tahoe)
|
||||
stopping.addErrback(
|
||||
# Let it fail because the process has already exited.
|
||||
lambda err: (
|
||||
err.trap(ProcessTerminated)
|
||||
and self.assertEqual(
|
||||
err.value.exitCode,
|
||||
COULD_NOT_STOP,
|
||||
)
|
||||
)
|
||||
)
|
||||
stopping = self.stop_and_wait()
|
||||
stopping.addErrback(self._check_cleanup_reason)
|
||||
return stopping
|
||||
|
||||
|
||||
|
@ -13,12 +13,13 @@ import attr
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.internet.error import (
|
||||
ProcessTerminated,
|
||||
)
|
||||
from twisted.internet import reactor
|
||||
from twisted.python import usage, runtime
|
||||
from twisted.internet.defer import inlineCallbacks, returnValue
|
||||
from twisted.internet.defer import (
|
||||
inlineCallbacks,
|
||||
returnValue,
|
||||
DeferredList,
|
||||
)
|
||||
from twisted.python.filepath import FilePath
|
||||
|
||||
from allmydata.util import fileutil, pollmixin
|
||||
@ -33,6 +34,7 @@ from .cli_node_api import (
|
||||
CLINodeAPI,
|
||||
Expect,
|
||||
on_stdout,
|
||||
on_stdout_and_stderr,
|
||||
wait_for_exit,
|
||||
)
|
||||
from ._twisted_9607 import (
|
||||
@ -604,11 +606,6 @@ class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
|
||||
basedir = self.workdir("test_client")
|
||||
c1 = os.path.join(basedir, "c1")
|
||||
|
||||
def stop_and_wait(tahoe):
|
||||
p, d = wait_for_exit()
|
||||
tahoe.stop(p)
|
||||
return d
|
||||
|
||||
tahoe = CLINodeAPI(reactor, FilePath(c1))
|
||||
# Set this up right now so we don't forget later.
|
||||
self.addCleanup(tahoe.cleanup)
|
||||
@ -641,7 +638,7 @@ class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
|
||||
|
||||
# rm this so we can detect when the second incarnation is ready
|
||||
tahoe.node_url_file.remove()
|
||||
yield stop_and_wait(tahoe)
|
||||
yield tahoe.stop_and_wait()
|
||||
|
||||
p = Expect()
|
||||
# We don't have to add another cleanup for this one, the one from
|
||||
@ -662,7 +659,7 @@ class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
|
||||
tahoe.twistd_pid_file.parent().listdir(),
|
||||
),
|
||||
)
|
||||
yield stop_and_wait(tahoe)
|
||||
yield tahoe.stop_and_wait()
|
||||
|
||||
# twistd.pid should be gone by now.
|
||||
self.assertFalse(tahoe.twistd_pid_file.exists())
|
||||
@ -672,36 +669,102 @@ class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
|
||||
fileutil.remove(file)
|
||||
return res
|
||||
|
||||
@skipIf(cannot_daemonize, cannot_daemonize)
|
||||
def test_baddir(self):
|
||||
basedir = self.workdir("test_baddir")
|
||||
def test_run_bad_directory(self):
|
||||
"""
|
||||
If ``tahoe run`` is pointed at a non-node directory, it reports an error
|
||||
and exits.
|
||||
"""
|
||||
return self._bad_directory_test(
|
||||
u"test_run_bad_directory",
|
||||
"tahoe run",
|
||||
lambda tahoe, p: tahoe.run(p),
|
||||
"is not a recognizable node directory",
|
||||
)
|
||||
|
||||
def test_run_bogus_directory(self):
|
||||
"""
|
||||
If ``tahoe run`` is pointed at a non-directory, it reports an error and
|
||||
exits.
|
||||
"""
|
||||
return self._bad_directory_test(
|
||||
u"test_run_bogus_directory",
|
||||
"tahoe run",
|
||||
lambda tahoe, p: CLINodeAPI(
|
||||
tahoe.reactor,
|
||||
tahoe.basedir.sibling(u"bogus"),
|
||||
).run(p),
|
||||
"does not look like a directory at all"
|
||||
)
|
||||
|
||||
def test_stop_bad_directory(self):
|
||||
"""
|
||||
If ``tahoe run`` is pointed at a directory where no node is running, it
|
||||
reports an error and exits.
|
||||
"""
|
||||
return self._bad_directory_test(
|
||||
u"test_stop_bad_directory",
|
||||
"tahoe stop",
|
||||
lambda tahoe, p: tahoe.stop(p),
|
||||
"does not look like a running node directory",
|
||||
)
|
||||
|
||||
@inline_callbacks
|
||||
def _bad_directory_test(self, workdir, description, operation, expected_message):
|
||||
"""
|
||||
Verify that a certain ``tahoe`` CLI operation produces a certain expected
|
||||
message and then exits.
|
||||
|
||||
:param unicode workdir: A distinct path name for this test to operate
|
||||
on.
|
||||
|
||||
:param unicode description: A description of the operation being
|
||||
performed.
|
||||
|
||||
:param operation: A two-argument callable implementing the operation.
|
||||
The first argument is a ``CLINodeAPI`` instance to use to perform
|
||||
the operation. The second argument is an ``IProcessProtocol`` to
|
||||
which the operations output must be delivered.
|
||||
|
||||
:param unicode expected_message: Some text that is expected in the
|
||||
stdout or stderr of the operation in the successful case.
|
||||
|
||||
:return: A ``Deferred`` that fires when the assertions have been made.
|
||||
"""
|
||||
basedir = self.workdir(workdir)
|
||||
fileutil.make_dirs(basedir)
|
||||
|
||||
d = self.run_bintahoe(["--quiet", "start", "--basedir", basedir])
|
||||
def _cb(res):
|
||||
out, err, rc_or_sig = res
|
||||
self.failUnlessEqual(rc_or_sig, 1)
|
||||
self.failUnless("is not a recognizable node directory" in err, err)
|
||||
d.addCallback(_cb)
|
||||
tahoe = CLINodeAPI(reactor, FilePath(basedir))
|
||||
# If tahoe ends up thinking it should keep running, make sure it stops
|
||||
# promptly when the test is done.
|
||||
self.addCleanup(tahoe.cleanup)
|
||||
|
||||
def _then_stop_it(res):
|
||||
return self.run_bintahoe(["--quiet", "stop", "--basedir", basedir])
|
||||
d.addCallback(_then_stop_it)
|
||||
p = Expect()
|
||||
operation(tahoe, on_stdout_and_stderr(p))
|
||||
|
||||
def _cb2(res):
|
||||
out, err, rc_or_sig = res
|
||||
self.failUnlessEqual(rc_or_sig, 2)
|
||||
self.failUnless("does not look like a running node directory" in err)
|
||||
d.addCallback(_cb2)
|
||||
client_running = p.expect(b"client running")
|
||||
|
||||
def _then_start_in_bogus_basedir(res):
|
||||
not_a_dir = os.path.join(basedir, "bogus")
|
||||
return self.run_bintahoe(["--quiet", "start", "--basedir", not_a_dir])
|
||||
d.addCallback(_then_start_in_bogus_basedir)
|
||||
result, index = yield DeferredList([
|
||||
p.expect(expected_message),
|
||||
client_running,
|
||||
], fireOnOneCallback=True, consumeErrors=True,
|
||||
)
|
||||
|
||||
def _cb3(res):
|
||||
out, err, rc_or_sig = res
|
||||
self.failUnlessEqual(rc_or_sig, 1)
|
||||
self.failUnlessIn("does not look like a directory at all", err)
|
||||
d.addCallback(_cb3)
|
||||
return d
|
||||
self.assertEqual(
|
||||
index,
|
||||
0,
|
||||
"Expected error message from '{}', got something else: {}".format(
|
||||
description,
|
||||
p.get_buffered_output(),
|
||||
),
|
||||
)
|
||||
|
||||
# It should not be running.
|
||||
self.assertFalse(tahoe.twistd_pid_file.exists())
|
||||
|
||||
# Wait for the operation to *complete*. If we got this far it's
|
||||
# because we got the expected message so we can expect the "tahoe ..."
|
||||
# child process to exit very soon. This other Deferred will fail when
|
||||
# it eventually does but DeferredList above will consume the error.
|
||||
# What's left is a perfect indicator that the process has exited and
|
||||
# we won't get blamed for leaving the reactor dirty.
|
||||
yield client_running
|
||||
|
Loading…
Reference in New Issue
Block a user