rewrite test_baddir as several tahoe run-using tests

This commit is contained in:
Jean-Paul Calderone 2019-05-03 08:55:35 -04:00
parent 57fc078383
commit 0e8472c017
2 changed files with 140 additions and 55 deletions

View File

@ -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,15 +61,21 @@ 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):
def childDataReceived(self, childFD, data):
if childFD in self._fds:
self._stdout_protocol.dataReceived(data)
def processEnded(self, reason):
@ -71,7 +83,10 @@ class _Stdout(ProcessProtocol):
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.
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 cleanup(self):
stopping = stop_and_wait(tahoe)
stopping.addErrback(
def _check_cleanup_reason(self, reason):
# Let it fail because the process has already exited.
lambda err: (
err.trap(ProcessTerminated)
and self.assertEqual(
err.value.exitCode,
COULD_NOT_STOP,
)
)
)
reason.trap(ProcessTerminated)
if reason.value.exitCode != COULD_NOT_STOP:
return reason
return None
def cleanup(self):
stopping = self.stop_and_wait()
stopping.addErrback(self._check_cleanup_reason)
return stopping

View File

@ -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