Browse files

appserver: test exec, don't buffer stdin

  • Loading branch information...
1 parent 2dd0659 commit 8e57f6e214bcc1e786e02e155be3ca7b991f183b @warner committed Jun 16, 2009
Showing with 129 additions and 66 deletions.
  1. +9 −0 ChangeLog
  2. +1 −1 bin/flappclient
  3. +0 −4 foolscap/appserver/cli.py
  4. +41 −57 foolscap/appserver/client.py
  5. +78 −4 foolscap/test/test_appserver.py
View
9 ChangeLog
@@ -1,5 +1,14 @@
2009-06-16 Brian Warner <warner@lothar.com>
+ * foolscap/appserver/cli.py: move flappclient code out of cli.py ..
+ * foolscap/appserver/client.py (run_flappclient): .. into client.py
+ (Exec.dataReceived): don't spin up StandardIO until after the
+ server tells us they want stdin. This also means we can stop
+ buffering stdin.
+ * foolscap/test/test_appserver.py: match client.run_flappclient
+ change
+ (RunCommand.test_run): first test of run-command/exec code
+
* foolscap/broker.py (Broker.doNextCall): don't run any calls
after we've been shut down. This fixes a racy bug in which two
calls arrive and get queued in the same input hunk, the first one
View
2 bin/flappclient
@@ -12,7 +12,7 @@ if (os.path.exists(os.path.join(base, "setup.py")) and
os.path.exists(os.path.join(base, "foolscap"))):
sys.path.insert(0, base)
-from foolscap.appserver.cli import run_flappclient
+from foolscap.appserver.client import run_flappclient
if __name__ == '__main__':
run_flappclient()
View
4 foolscap/appserver/cli.py
@@ -484,7 +484,3 @@ def done(rc):
return r
else:
return (r, so.stdout.getvalue(), so.stderr.getvalue())
-
-def run_flappclient(argv=None, run_by_human=True):
- from foolscap.appserver import client
- return client.run_cli(argv, run_by_human)
View
98 foolscap/appserver/client.py
@@ -40,46 +40,29 @@ def run(self, rref, options):
self.done = False
self.d = defer.Deferred()
rref.notifyOnDisconnect(self._done, 3)
- self.buffered_stdin = []
- self.stdin_closed = False
self.stdin_writer = None
- stdio = options.stdio
- stdio(self)
+ self.stdio = options.stdio
self.stdout = options.stdout
self.stderr = options.stderr
d = rref.callRemote("execute", self)
d.addCallback(self._started)
d.addErrback(self._err)
return self.d
- #def makeConnection(self):
- # pass
def dataReceived(self, data):
- # this is from stdin
- if self.stdin_writer is None:
- self.buffered_stdin.append(data)
- elif self.stdin_writer is False:
- pass # discard
- else:
- self.stdin_writer.callRemoteOnly("feed_stdin", data)
+ # this is from stdin. It shouldn't be called until after _started
+ # sets up stdio and self.stdin_writer
+ self.stdin_writer.callRemoteOnly("feed_stdin", data)
def connectionLost(self, reason):
- self.stdin_closed = True
- if self.stdin_writer:
- self.stdin_writer.callRemoteOnly("close_stdin")
+ # likewise, this won't be called unless _started wanted stdin
+ self.stdin_writer.callRemoteOnly("close_stdin")
def _started(self, stdin_writer):
if stdin_writer:
self.stdin_writer = stdin_writer # rref
- if self.buffered_stdin:
- self.stdin_writer.callRemoteOnly("feed_stdin",
- "".join(self.buffered_stdin))
- if self.stdin_closed:
- self.stdin_writer.callRemoteOnly("close_stdin")
- else:
- # they don't want our stdin
- self.stdin_writer = False
- del self.buffered_stdin
+ self.stdio(self) # start accepting stdin
+ # otherwise they don't want our stdin, so leave stdin_writer=None
def remote_stdout(self, data):
self.stdout.write(data)
@@ -143,7 +126,39 @@ def opt_version(self):
"exec": Exec,
}
-def run_cli(argv=None, run_by_human=True, stdio=StandardIO):
+
+def parse_options(command_name, argv, stdio, stdout, stderr):
+ try:
+ config = ClientOptions()
+ config.stdout = stdout
+ config.stderr = stderr
+ config.parseOptions(argv)
+
+ config.subOptions.stdio = stdio # for streaming input
+ config.subOptions.stdout = stdout
+ config.subOptions.stderr = stderr
+
+ except usage.error, e:
+ print >>stderr, "%s: %s" % (command_name, e)
+ print >>stderr
+ c = getattr(config, 'subOptions', config)
+ print >>stderr, str(c)
+ sys.exit(1)
+
+ return config
+
+def run_command(config):
+ c = dispatch_table[config.subCommand]()
+ tub = Tub()
+ d = defer.succeed(None)
+ d.addCallback(lambda _ign: tub.startService())
+ d.addCallback(lambda _ign: tub.getReference(config.furl))
+ d.addCallback(c.run, config.subOptions) # might provide tub here
+ d.addBoth(lambda res: tub.stopService().addCallback(lambda _ign: res))
+ return d
+
+
+def run_flappclient(argv=None, run_by_human=True, stdio=StandardIO):
if run_by_human:
stdout = sys.stdout
stderr = sys.stderr
@@ -187,34 +202,3 @@ def done(rc):
return (rc, stdout.getvalue(), stderr.getvalue())
d.addCallback(done)
return d
-
-
-def parse_options(command_name, argv, stdio, stdout, stderr):
- try:
- config = ClientOptions()
- config.stdout = stdout
- config.stderr = stderr
- config.parseOptions(argv)
-
- config.subOptions.stdio = stdio # for streaming input
- config.subOptions.stdout = stdout
- config.subOptions.stderr = stderr
-
- except usage.error, e:
- print >>stderr, "%s: %s" % (command_name, e)
- print >>stderr
- c = getattr(config, 'subOptions', config)
- print >>stderr, str(c)
- sys.exit(1)
-
- return config
-
-def run_command(config):
- c = dispatch_table[config.subCommand]()
- tub = Tub()
- d = defer.succeed(None)
- d.addCallback(lambda _ign: tub.startService())
- d.addCallback(lambda _ign: tub.getReference(config.furl))
- d.addCallback(c.run, config.subOptions) # might provide tub here
- d.addBoth(lambda res: tub.stopService().addCallback(lambda _ign: res))
- return d
View
82 foolscap/test/test_appserver.py
@@ -6,8 +6,8 @@
from twisted.application import service
from foolscap.api import Tub
-from foolscap.appserver import cli, server
-from foolscap.test.common import ShouldFailMixin, crypto_available
+from foolscap.appserver import cli, server, client
+from foolscap.test.common import ShouldFailMixin, crypto_available, StallMixin
class RequiresCryptoBase:
def setUp(self):
@@ -327,7 +327,7 @@ def run_cli(self, *args):
def run_client(self, *args):
argv = ["flappclient"] + list(args)
- d = defer.maybeDeferred(cli.run_flappclient, argv=argv, run_by_human=False)
+ d = defer.maybeDeferred(client.run_flappclient, argv=argv, run_by_human=False)
return d # fires with (rc,out,err)
def test_run(self):
@@ -417,7 +417,7 @@ class Client(unittest.TestCase):
def run_client(self, *args):
argv = ["flappclient"] + list(args)
- d = defer.maybeDeferred(cli.run_flappclient, argv=argv, run_by_human=False)
+ d = defer.maybeDeferred(client.run_flappclient, argv=argv, run_by_human=False)
return d # fires with (rc,out,err)
def test_no_command(self):
@@ -451,3 +451,77 @@ def _check_client((rc,out,err)):
self.failUnlessEqual("", err.strip())
d.addCallback(_check_client)
return d
+
+class RunCommand(unittest.TestCase, RequiresCryptoBase, StallMixin):
+ def setUp(self):
+ RequiresCryptoBase.setUp(self)
+ self.s = service.MultiService()
+ self.s.startService()
+ def tearDown(self):
+ return self.s.stopService()
+
+ def run_cli(self, *args):
+ argv = ["flappserver"] + list(args)
+ d = defer.maybeDeferred(cli.run_flappserver, argv=argv, run_by_human=False)
+ return d # fires with (rc,out,err)
+
+ def run_client(self, *args):
+ argv = ["flappclient"] + list(args)
+ d = defer.maybeDeferred(client.run_flappclient,
+ argv=argv, run_by_human=False,
+ stdio=None)
+ return d # fires with (rc,out,err)
+
+ def test_run(self):
+ basedir = "appserver/RunCommand/run"
+ os.makedirs(basedir)
+ serverdir = os.path.join(basedir, "fl")
+ incomingdir = os.path.join(basedir, "incoming")
+ os.mkdir(incomingdir)
+ furlfile = os.path.join(basedir, "furlfile")
+
+ d = self.run_cli("create", serverdir)
+ def _check((rc,out,err)):
+ self.failUnlessEqual(rc, 0)
+ self.failUnless(os.path.isdir(serverdir))
+ d.addCallback(_check)
+ targetfile = os.path.join(incomingdir, "foo.txt")
+ f = open(targetfile, "wb")
+ DATA = "Contents of foo.txt.\n"
+ f.write(DATA)
+ f.close()
+ d.addCallback(lambda ign:
+ self.run_cli("add", serverdir,
+ "exec", incomingdir, "cat", "foo.txt"))
+ def _check_add((rc,out,err)):
+ self.failUnlessEqual(rc, 0)
+ lines = out.splitlines()
+ self.failUnless(lines[1].startswith("FURL is pb://"))
+ self.furl = lines[1].split()[-1]
+ f = open(furlfile,"w")
+ f.write(self.furl+"\n")
+ f.close()
+ d.addCallback(_check_add)
+ stdout = StringIO()
+ def _start_server(ign):
+ ap = server.AppServer(serverdir, stdout)
+ ap.setServiceParent(self.s)
+ return ap.when_ready()
+ d.addCallback(_start_server)
+
+ d.addCallback(lambda _ign: self.run_client("--furl", self.furl, "exec"))
+ def _check_client((rc,out,err)):
+ self.failUnlessEqual(rc, 0)
+ self.failUnlessEqual(out, DATA)
+ self.failUnlessEqual(err.strip(), "")
+ d.addCallback(_check_client)
+
+ d.addCallback(lambda _ign: os.unlink(targetfile))
+ d.addCallback(lambda _ign: self.run_client("--furl", self.furl, "exec"))
+ def _check_client2((rc,out,err)):
+ self.failIfEqual(rc, 0)
+ self.failUnlessEqual(out, "")
+ self.failUnlessEqual(err.strip(), "cat: foo.txt: No such file or directory")
+ d.addCallback(_check_client2)
+
+ return d

0 comments on commit 8e57f6e

Please sign in to comment.