← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:sftp-inlineCallbacks into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:sftp-inlineCallbacks into launchpad:master.

Commit message:
Convert lp.codehosting.sftp and its tests to inlineCallbacks

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/393769

This is more compact and easier to follow.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:sftp-inlineCallbacks into launchpad:master.
diff --git a/lib/lp/codehosting/sftp.py b/lib/lp/codehosting/sftp.py
index d99dcc6..614f847 100644
--- a/lib/lp/codehosting/sftp.py
+++ b/lib/lp/codehosting/sftp.py
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """An SFTP server that backs on to a special kind of Bazaar Transport.
@@ -166,6 +166,7 @@ class TransportSFTPFile:
         return self.transport.put_bytes(self._escaped_path, b'')
 
     @with_sftp_error
+    @defer.inlineCallbacks
     def writeChunk(self, offset, data):
         """See `ISFTPFile`."""
         if not self._shouldWrite():
@@ -173,43 +174,30 @@ class TransportSFTPFile:
                 filetransfer.FX_PERMISSION_DENIED,
                 "%r was opened read-only." % self._unescaped_relpath)
         if self._shouldTruncate():
-            deferred = self._truncateFile()
-        else:
-            deferred = defer.succeed(None)
+            yield self._truncateFile()
         self._written = True
         if self._shouldAppend():
-            deferred.addCallback(
-                lambda ignored:
-                self.transport.append_bytes(self._escaped_path, data))
+            yield self.transport.append_bytes(self._escaped_path, data)
         else:
-            deferred.addCallback(
-                lambda ignored:
-                self.transport.writeChunk(self._escaped_path, offset, data))
-        return deferred
+            yield self.transport.writeChunk(self._escaped_path, offset, data)
 
     @with_sftp_error
+    @defer.inlineCallbacks
     def readChunk(self, offset, length):
         """See `ISFTPFile`."""
-        deferred = self.transport.readv(
-            self._escaped_path, [(offset, length)])
-
-        def get_first_chunk(read_things):
-            return next(read_things)[1]
-
-        def handle_short_read(failure):
-            """Handle short reads by reading what was available.
-
-            Doing things this way around, by trying to read all the data
-            requested and then handling the short read error, might be a bit
-            inefficient, but the breezy sftp transport doesn't read past the
-            end of files, so we don't need to worry too much about performance
-            here.
-            """
-            failure.trap(bzr_errors.ShortReadvError)
-            return self.readChunk(failure.value.offset, failure.value.actual)
-
-        deferred.addCallback(get_first_chunk)
-        return deferred.addErrback(handle_short_read)
+        try:
+            read_things = yield self.transport.readv(
+                self._escaped_path, [(offset, length)])
+            chunk = next(read_things)[1]
+        except bzr_errors.ShortReadvError as e:
+            # Handle short reads by reading what was available.
+            # Doing things this way around, by trying to read all the data
+            # requested and then handling the short read error, might be a
+            # bit inefficient, but the breezy sftp transport doesn't read
+            # past the end of files, so we don't need to worry too much
+            # about performance here.
+            chunk = yield self.readChunk(e.offset, e.actual)
+        defer.returnValue(chunk)
 
     def setAttrs(self, attrs):
         """See `ISFTPFile`.
@@ -225,21 +213,19 @@ class TransportSFTPFile:
         """See `ISFTPFile`."""
         return self._server.getAttrs(self._unescaped_relpath, False)
 
+    @defer.inlineCallbacks
     def close(self):
         """See `ISFTPFile`."""
         if self._written or not self._shouldCreate():
-            return defer.succeed(None)
+            return
 
         if self._shouldTruncate():
-            return self._truncateFile()
-
-        deferred = self.transport.has(self._escaped_path)
-
-        def maybe_create_file(already_exists):
-            if not already_exists:
-                return self._truncateFile()
+            yield self._truncateFile()
+            return
 
-        return deferred.addCallback(maybe_create_file)
+        already_exists = yield self.transport.has(self._escaped_path)
+        if not already_exists:
+            yield self._truncateFile()
 
 
 def _get_transport_for_dir(directory):
@@ -311,48 +297,38 @@ class TransportSFTPServer:
             yield (shortname, longname, attr_dict)
 
     @with_sftp_error
+    @defer.inlineCallbacks
     def openDirectory(self, path):
         """See `ISFTPServer`."""
         escaped_path = urlutils.escape(path)
-        deferred = self.transport.list_dir(escaped_path)
-
-        def produce_entries_from_file_list(file_list):
-            stats_deferred = self._stat_files_in_list(file_list, escaped_path)
-            stats_deferred.addCallback(
-                self._format_directory_entries, file_list)
-            return stats_deferred
-
-        return deferred.addCallback(
-            produce_entries_from_file_list).addCallback(DirectoryListing)
+        file_list = yield self.transport.list_dir(escaped_path)
+        stat_results = yield self._stat_files_in_list(file_list, escaped_path)
+        entries = self._format_directory_entries(stat_results, file_list)
+        defer.returnValue(DirectoryListing(entries))
 
     @with_sftp_error
+    @defer.inlineCallbacks
     def openFile(self, path, flags, attrs):
         """See `ISFTPServer`."""
         directory = os.path.dirname(path)
-        deferred = self.transport.stat(directory)
-
-        def open_file(stat_result):
-            if stat.S_ISDIR(stat_result.st_mode):
-                return TransportSFTPFile(self.transport, path, flags, self)
-            else:
-                raise filetransfer.SFTPError(
-                    filetransfer.FX_NO_SUCH_FILE, directory)
-
-        return deferred.addCallback(open_file)
+        stat_result = yield self.transport.stat(directory)
+        if stat.S_ISDIR(stat_result.st_mode):
+            defer.returnValue(
+                TransportSFTPFile(self.transport, path, flags, self))
+        else:
+            raise filetransfer.SFTPError(
+                filetransfer.FX_NO_SUCH_FILE, directory)
 
     def readLink(self, path):
         """See `ISFTPServer`."""
         raise NotImplementedError()
 
+    @defer.inlineCallbacks
     def realPath(self, relpath):
         """See `ISFTPServer`."""
-        deferred = self.transport.local_realPath(urlutils.escape(relpath))
-
-        def unescape_path(path):
-            unescaped_path = urlutils.unescape(path)
-            return unescaped_path.encode('utf-8')
-
-        return deferred.addCallback(unescape_path)
+        path = yield self.transport.local_realPath(urlutils.escape(relpath))
+        unescaped_path = urlutils.unescape(path)
+        defer.returnValue(unescaped_path.encode('utf-8'))
 
     def setAttrs(self, path, attrs):
         """See `ISFTPServer`.
@@ -378,13 +354,14 @@ class TransportSFTPServer:
         }
 
     @with_sftp_error
+    @defer.inlineCallbacks
     def getAttrs(self, path, followLinks):
         """See `ISFTPServer`.
 
         This just delegates to TransportSFTPFile's implementation.
         """
-        deferred = self.transport.stat(urlutils.escape(path))
-        return deferred.addCallback(self._translate_stat)
+        stat_result = yield self.transport.stat(urlutils.escape(path))
+        defer.returnValue(self._translate_stat(stat_result))
 
     def gotVersion(self, otherVersion, extensionData):
         """See `ISFTPServer`."""
diff --git a/lib/lp/codehosting/tests/test_sftp.py b/lib/lp/codehosting/tests/test_sftp.py
index d827421..794fb47 100644
--- a/lib/lp/codehosting/tests/test_sftp.py
+++ b/lib/lp/codehosting/tests/test_sftp.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for the transport-backed SFTP server implementation."""
@@ -16,10 +16,9 @@ from breezy.tests import TestCaseInTempDir
 from breezy.transport import get_transport
 from breezy.transport.memory import MemoryTransport
 from lazr.sshserver.sftp import FileIsADirectory
-from testtools.twistedsupport import (
-    assert_fails_with,
-    AsynchronousDeferredRunTest,
-    )
+from testtools.matchers import MatchesStructure
+from testtools.testcase import ExpectedException
+from testtools.twistedsupport import AsynchronousDeferredRunTest
 from twisted.conch.interfaces import ISFTPServer
 from twisted.conch.ls import lsLine
 from twisted.conch.ssh import filetransfer
@@ -184,16 +183,13 @@ class TestSFTPFile(TestCaseInTempDir, SFTPTestMixin):
             FatLocalTransport(urlutils.local_path_to_url('.')))
         self._sftp_server = TransportSFTPServer(transport)
 
+    @defer.inlineCallbacks
     def assertSFTPError(self, sftp_code, function, *args, **kwargs):
         """Assert that calling functions fails with `sftp_code`."""
-        deferred = defer.maybeDeferred(function, *args, **kwargs)
-        deferred = assert_fails_with(deferred, filetransfer.SFTPError)
-
-        def check_sftp_code(exception):
-            self.assertEqual(sftp_code, exception.code)
-            return exception
-
-        return deferred.addCallback(check_sftp_code)
+        with ExpectedException(
+                filetransfer.SFTPError,
+                MatchesStructure.byEquality(code=sftp_code)):
+            yield function(*args, **kwargs)
 
     def openFile(self, path, flags, attrs):
         return self._sftp_server.openFile(path, flags, attrs)
@@ -218,213 +214,183 @@ class TestSFTPFile(TestCaseInTempDir, SFTPTestMixin):
             self.openFile,
             '%s/%s' % (nondirectory, self.getPathSegment()), 0, {})
 
+    @defer.inlineCallbacks
     def test_createEmptyFile(self):
         # Opening a file with create flags and then closing it will create a
         # new, empty file.
         filename = self.getPathSegment()
-        deferred = self.openFile(filename, filetransfer.FXF_CREAT, {})
-        return deferred.addCallback(
-            self._test_createEmptyFile_callback, filename)
-
-    def _test_createEmptyFile_callback(self, handle, filename):
-        deferred = handle.close()
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual('', filename))
+        handle = yield self.openFile(filename, filetransfer.FXF_CREAT, {})
+        yield handle.close()
+        self.assertFileEqual('', filename)
 
+    @defer.inlineCallbacks
     def test_createFileWithData(self):
         # writeChunk writes data to the file.
         filename = self.getPathSegment()
-        deferred = self.openFile(
+        handle = yield self.openFile(
             filename, filetransfer.FXF_CREAT | filetransfer.FXF_WRITE, {})
-        return deferred.addCallback(
-            self._test_createFileWithData_callback, filename)
-
-    def _test_createFileWithData_callback(self, handle, filename):
-        deferred = handle.writeChunk(0, 'bar')
-        deferred.addCallback(lambda ignored: handle.close())
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual('bar', filename))
+        yield handle.writeChunk(0, 'bar')
+        yield handle.close()
+        self.assertFileEqual('bar', filename)
 
+    @defer.inlineCallbacks
     def test_writeChunkToFile(self):
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'contents')])
-        deferred = self.openFile(filename, filetransfer.FXF_WRITE, {})
-        return deferred.addCallback(
-            self._test_writeChunkToFile_callback, filename)
-
-    def _test_writeChunkToFile_callback(self, handle, filename):
-        deferred = handle.writeChunk(1, 'qux')
-        deferred.addCallback(lambda ignored: handle.close())
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual('cquxents', filename))
+        handle = yield self.openFile(filename, filetransfer.FXF_WRITE, {})
+        yield handle.writeChunk(1, 'qux')
+        yield handle.close()
+        self.assertFileEqual('cquxents', filename)
 
+    @defer.inlineCallbacks
     def test_writeTwoChunks(self):
         # We can write one chunk after another.
         filename = self.getPathSegment()
-        deferred = self.openFile(
+        handle = yield self.openFile(
             filename, filetransfer.FXF_WRITE | filetransfer.FXF_TRUNC, {})
+        yield handle.writeChunk(1, 'a')
+        yield handle.writeChunk(2, 'a')
+        yield handle.close()
+        self.assertFileEqual(chr(0) + 'aa', filename)
 
-        def write_chunks(handle):
-            deferred = handle.writeChunk(1, 'a')
-            deferred.addCallback(lambda ignored: handle.writeChunk(2, 'a'))
-            deferred.addCallback(lambda ignored: handle.close())
-
-        deferred.addCallback(write_chunks)
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual(chr(0) + 'aa', filename))
-
+    @defer.inlineCallbacks
     def test_writeChunkToNonexistentFile(self):
         # Writing a chunk of data to a non-existent file creates the file even
         # if the create flag is not set. NOTE: This behaviour is unspecified
         # in the SFTP drafts at
         # http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/
         filename = self.getPathSegment()
-        deferred = self.openFile(filename, filetransfer.FXF_WRITE, {})
-        return deferred.addCallback(
-            self._test_writeChunkToNonexistentFile_callback, filename)
-
-    def _test_writeChunkToNonexistentFile_callback(self, handle, filename):
-        deferred = handle.writeChunk(1, 'qux')
-        deferred.addCallback(lambda ignored: handle.close())
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual(chr(0) + 'qux', filename))
+        handle = yield self.openFile(filename, filetransfer.FXF_WRITE, {})
+        yield handle.writeChunk(1, 'qux')
+        yield handle.close()
+        self.assertFileEqual(chr(0) + 'qux', filename)
 
+    @defer.inlineCallbacks
     def test_writeToReadOpenedFile(self):
         # writeChunk raises an error if we try to write to a file that has
         # been opened only for reading.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(filename, filetransfer.FXF_READ, {})
-        return deferred.addCallback(
-            self._test_writeToReadOpenedFile_callback)
-
-    def _test_writeToReadOpenedFile_callback(self, handle):
-        return self.assertSFTPError(
+        handle = yield self.openFile(filename, filetransfer.FXF_READ, {})
+        yield self.assertSFTPError(
             filetransfer.FX_PERMISSION_DENIED,
             handle.writeChunk, 0, 'new content')
 
+    @defer.inlineCallbacks
     def test_overwriteFile(self):
         # writeChunk overwrites a file if write, create and trunk flags are
         # set.
         self.build_tree_contents([('foo', 'contents')])
-        deferred = self.openFile(
+        handle = yield self.openFile(
             'foo', filetransfer.FXF_CREAT | filetransfer.FXF_TRUNC |
             filetransfer.FXF_WRITE, {})
-        return deferred.addCallback(self._test_overwriteFile_callback)
-
-    def _test_overwriteFile_callback(self, handle):
-        deferred = handle.writeChunk(0, 'bar')
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual('bar', 'foo'))
+        yield handle.writeChunk(0, 'bar')
+        self.assertFileEqual('bar', 'foo')
 
+    @defer.inlineCallbacks
     def test_writeToAppendingFileIgnoresOffset(self):
         # If a file is opened with the 'append' flag, writeChunk ignores its
         # offset parameter.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(filename, filetransfer.FXF_APPEND, {})
-        return deferred.addCallback(
-            self._test_writeToAppendingFileIgnoresOffset_cb, filename)
-
-    def _test_writeToAppendingFileIgnoresOffset_cb(self, handle, filename):
-        deferred = handle.writeChunk(0, 'baz')
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual('barbaz', filename))
+        handle = yield self.openFile(filename, filetransfer.FXF_APPEND, {})
+        yield handle.writeChunk(0, 'baz')
+        self.assertFileEqual('barbaz', filename)
 
+    @defer.inlineCallbacks
     def test_openAndCloseExistingFileLeavesUnchanged(self):
         # If we open a file with the 'create' flag and without the 'truncate'
         # flag, the file remains unchanged.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(filename, filetransfer.FXF_CREAT, {})
-        return deferred.addCallback(
-            self._test_openAndCloseExistingFileUnchanged_cb, filename)
-
-    def _test_openAndCloseExistingFileUnchanged_cb(self, handle, filename):
-        deferred = handle.close()
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual('bar', filename))
+        handle = yield self.openFile(filename, filetransfer.FXF_CREAT, {})
+        yield handle.close()
+        self.assertFileEqual('bar', filename)
 
+    @defer.inlineCallbacks
     def test_openAndCloseExistingFileTruncation(self):
         # If we open a file with the 'create' flag and the 'truncate' flag,
         # the file is reset to empty.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(
+        handle = yield self.openFile(
             filename, filetransfer.FXF_TRUNC | filetransfer.FXF_CREAT, {})
-        return deferred.addCallback(
-            self._test_openAndCloseExistingFileTruncation_cb, filename)
-
-    def _test_openAndCloseExistingFileTruncation_cb(self, handle, filename):
-        deferred = handle.close()
-        return deferred.addCallback(
-            lambda ignored: self.assertFileEqual('', filename))
+        yield handle.close()
+        self.assertFileEqual('', filename)
 
+    @defer.inlineCallbacks
     def test_writeChunkOnDirectory(self):
         # Errors in writeChunk are translated to SFTPErrors.
         directory = self.getPathSegment()
         os.mkdir(directory)
-        deferred = self.openFile(directory, filetransfer.FXF_WRITE, {})
-        deferred.addCallback(lambda handle: handle.writeChunk(0, 'bar'))
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        handle = yield self.openFile(directory, filetransfer.FXF_WRITE, {})
+        with ExpectedException(filetransfer.SFTPError):
+            yield handle.writeChunk(0, 'bar')
 
+    @defer.inlineCallbacks
     def test_readChunk(self):
         # readChunk reads a chunk of data from the file.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(filename, 0, {})
-        deferred.addCallback(lambda handle: handle.readChunk(1, 2))
-        return deferred.addCallback(self.assertEqual, 'ar')
+        handle = yield self.openFile(filename, 0, {})
+        chunk = yield handle.readChunk(1, 2)
+        self.assertEqual('ar', chunk)
 
+    @defer.inlineCallbacks
     def test_readChunkPastEndOfFile(self):
         # readChunk returns the rest of the file if it is asked to read past
         # the end of the file.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(filename, 0, {})
-        deferred.addCallback(lambda handle: handle.readChunk(2, 10))
-        return deferred.addCallback(self.assertEqual, 'r')
+        handle = yield self.openFile(filename, 0, {})
+        chunk = yield handle.readChunk(2, 10)
+        self.assertEqual('r', chunk)
 
+    @defer.inlineCallbacks
     def test_readChunkEOF(self):
         # readChunk returns the empty string if it encounters end-of-file
         # before reading any data.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(filename, 0, {})
-        deferred.addCallback(lambda handle: handle.readChunk(3, 10))
-        return deferred.addCallback(self.assertEqual, '')
+        handle = yield self.openFile(filename, 0, {})
+        chunk = yield handle.readChunk(3, 10)
+        self.assertEqual('', chunk)
 
+    @defer.inlineCallbacks
     def test_readChunkError(self):
         # Errors in readChunk are translated to SFTPErrors.
         filename = self.getPathSegment()
-        deferred = self.openFile(filename, 0, {})
-        deferred.addCallback(lambda handle: handle.readChunk(1, 2))
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        handle = yield self.openFile(filename, 0, {})
+        with ExpectedException(filetransfer.SFTPError):
+            yield handle.readChunk(1, 2)
 
+    @defer.inlineCallbacks
     def test_setAttrs(self):
         # setAttrs on TransportSFTPFile does nothing.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.openFile(filename, 0, {})
-        return deferred.addCallback(lambda handle: handle.setAttrs({}))
+        handle = yield self.openFile(filename, 0, {})
+        yield handle.setAttrs({})
 
+    @defer.inlineCallbacks
     def test_getAttrs(self):
         # getAttrs on TransportSFTPFile returns a dictionary consistent
         # with the results of os.stat.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
         stat_value = os.stat(filename)
-        deferred = self.openFile(filename, 0, {})
-        deferred.addCallback(lambda handle: handle.getAttrs())
-        return deferred.addCallback(self.checkAttrs, stat_value)
+        handle = yield self.openFile(filename, 0, {})
+        attrs = yield handle.getAttrs()
+        self.checkAttrs(attrs, stat_value)
 
+    @defer.inlineCallbacks
     def test_getAttrsError(self):
         # Errors in getAttrs on TransportSFTPFile are translated into
         # SFTPErrors.
         filename = self.getPathSegment()
-        deferred = self.openFile(filename, 0, {})
-        deferred.addCallback(lambda handle: handle.getAttrs())
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        handle = yield self.openFile(filename, 0, {})
+        with ExpectedException(filetransfer.SFTPError):
+            yield handle.getAttrs()
 
 
 class TestSFTPServer(TestCaseInTempDir, SFTPTestMixin):
@@ -439,109 +405,104 @@ class TestSFTPServer(TestCaseInTempDir, SFTPTestMixin):
             FatLocalTransport(urlutils.local_path_to_url('.')))
         self.sftp_server = TransportSFTPServer(transport)
 
+    @defer.inlineCallbacks
     def test_serverSetAttrs(self):
         # setAttrs on the TransportSFTPServer doesn't do anything either.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        self.sftp_server.setAttrs(filename, {})
+        yield self.sftp_server.setAttrs(filename, {})
 
+    @defer.inlineCallbacks
     def test_serverGetAttrs(self):
         # getAttrs on the TransportSFTPServer also returns a dictionary
         # consistent with the results of os.stat.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
         stat_value = os.stat(filename)
-        deferred = self.sftp_server.getAttrs(filename, False)
-        return deferred.addCallback(self.checkAttrs, stat_value)
+        attrs = yield self.sftp_server.getAttrs(filename, False)
+        self.checkAttrs(attrs, stat_value)
 
+    @defer.inlineCallbacks
     def test_serverGetAttrsError(self):
         # Errors in getAttrs on the TransportSFTPServer are translated into
         # SFTPErrors.
         nonexistent_file = self.getPathSegment()
-        deferred = self.sftp_server.getAttrs(nonexistent_file, False)
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        with ExpectedException(filetransfer.SFTPError):
+            yield self.sftp_server.getAttrs(nonexistent_file, False)
 
+    @defer.inlineCallbacks
     def test_removeFile(self):
         # removeFile removes the file.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename, 'bar')])
-        deferred = self.sftp_server.removeFile(filename)
-
-        def assertFileRemoved(ignored):
-            self.assertFalse(file_exists(filename))
-
-        return deferred.addCallback(assertFileRemoved)
+        yield self.sftp_server.removeFile(filename)
+        self.assertFalse(file_exists(filename))
 
+    @defer.inlineCallbacks
     def test_removeFileError(self):
         # Errors in removeFile are translated into SFTPErrors.
         filename = self.getPathSegment()
-        deferred = self.sftp_server.removeFile(filename)
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        with ExpectedException(filetransfer.SFTPError):
+            yield self.sftp_server.removeFile(filename)
 
+    @defer.inlineCallbacks
     def test_removeFile_directory(self):
         # Errors in removeFile are translated into SFTPErrors.
         filename = self.getPathSegment()
         self.build_tree_contents([(filename + '/',)])
-        deferred = self.sftp_server.removeFile(filename)
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        with ExpectedException(filetransfer.SFTPError):
+            yield self.sftp_server.removeFile(filename)
 
+    @defer.inlineCallbacks
     def test_renameFile(self):
         # renameFile renames the file.
         orig_filename = self.getPathSegment()
         new_filename = self.getPathSegment()
         self.build_tree_contents([(orig_filename, 'bar')])
-        deferred = self.sftp_server.renameFile(orig_filename, new_filename)
-
-        def assertFileRenamed(ignored):
-            self.assertFalse(file_exists(orig_filename))
-            self.assertTrue(file_exists(new_filename))
-
-        return deferred.addCallback(assertFileRenamed)
+        yield self.sftp_server.renameFile(orig_filename, new_filename)
+        self.assertFalse(file_exists(orig_filename))
+        self.assertTrue(file_exists(new_filename))
 
+    @defer.inlineCallbacks
     def test_renameFileError(self):
         # Errors in renameFile are translated into SFTPErrors.
         orig_filename = self.getPathSegment()
         new_filename = self.getPathSegment()
-        deferred = self.sftp_server.renameFile(orig_filename, new_filename)
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        with ExpectedException(filetransfer.SFTPError):
+            yield self.sftp_server.renameFile(orig_filename, new_filename)
 
+    @defer.inlineCallbacks
     def test_makeDirectory(self):
         # makeDirectory makes the directory.
         directory = self.getPathSegment()
-        deferred = self.sftp_server.makeDirectory(
-            directory, {'permissions': 0o777})
-
-        def assertDirectoryExists(ignored):
-            self.assertTrue(
-                os.path.isdir(directory), '%r is not a directory' % directory)
-            self.assertEqual(0o40777, os.stat(directory).st_mode)
-
-        return deferred.addCallback(assertDirectoryExists)
+        yield self.sftp_server.makeDirectory(directory, {'permissions': 0o777})
+        self.assertTrue(
+            os.path.isdir(directory), '%r is not a directory' % directory)
+        self.assertEqual(0o40777, os.stat(directory).st_mode)
 
+    @defer.inlineCallbacks
     def test_makeDirectoryError(self):
         # Errors in makeDirectory are translated into SFTPErrors.
         nonexistent = self.getPathSegment()
         nonexistent_child = '%s/%s' % (nonexistent, self.getPathSegment())
-        deferred = self.sftp_server.makeDirectory(
-            nonexistent_child, {'permissions': 0o777})
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        with ExpectedException(filetransfer.SFTPError):
+            yield self.sftp_server.makeDirectory(
+                nonexistent_child, {'permissions': 0o777})
 
+    @defer.inlineCallbacks
     def test_removeDirectory(self):
         # removeDirectory removes the directory.
         directory = self.getPathSegment()
         os.mkdir(directory)
-        deferred = self.sftp_server.removeDirectory(directory)
-
-        def assertDirectoryRemoved(ignored):
-            self.assertFalse(file_exists(directory))
-
-        return deferred.addCallback(assertDirectoryRemoved)
+        yield self.sftp_server.removeDirectory(directory)
+        self.assertFalse(file_exists(directory))
 
+    @defer.inlineCallbacks
     def test_removeDirectoryError(self):
         # Errors in removeDirectory are translated into SFTPErrors.
         directory = self.getPathSegment()
-        deferred = self.sftp_server.removeDirectory(directory)
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        with ExpectedException(filetransfer.SFTPError):
+            yield self.sftp_server.removeDirectory(directory)
 
     def test_gotVersion(self):
         # gotVersion returns an empty dictionary.
@@ -554,12 +515,13 @@ class TestSFTPServer(TestCaseInTempDir, SFTPTestMixin):
             NotImplementedError, self.sftp_server.extendedRequest,
             'foo', 'bar')
 
+    @defer.inlineCallbacks
     def test_realPath(self):
         # realPath returns the absolute path of the file.
         src, dst = self.getPathSegment(), self.getPathSegment()
         os.symlink(src, dst)
-        deferred = self.sftp_server.realPath(dst)
-        return deferred.addCallback(self.assertEqual, os.path.abspath(src))
+        path = yield self.sftp_server.realPath(dst)
+        self.assertEqual(os.path.abspath(src), path)
 
     def test_makeLink(self):
         # makeLink is not supported.
@@ -573,6 +535,7 @@ class TestSFTPServer(TestCaseInTempDir, SFTPTestMixin):
             NotImplementedError, self.sftp_server.readLink,
             self.getPathSegment())
 
+    @defer.inlineCallbacks
     def test_openDirectory(self):
         # openDirectory returns an iterator that iterates over the contents of
         # the directory.
@@ -583,7 +546,11 @@ class TestSFTPServer(TestCaseInTempDir, SFTPTestMixin):
             parent_dir + '/',
             '%s/%s/' % (parent_dir, child_dir),
             '%s/%s' % (parent_dir, child_file)])
-        deferred = self.sftp_server.openDirectory(parent_dir)
+        directory = yield self.sftp_server.openDirectory(parent_dir)
+        entries = list(directory)
+        directory.close()
+        names = [entry[0] for entry in entries]
+        self.assertEqual(set(names), set([child_dir, child_file]))
 
         def check_entry(entries, filename):
             t = get_transport('.')
@@ -595,35 +562,26 @@ class TestSFTPServer(TestCaseInTempDir, SFTPTestMixin):
             self.assertEqual(lsLine(name, stat), longname)
             self.assertEqual(self.sftp_server._translate_stat(stat), attrs)
 
-        def check_open_directory(directory):
-            entries = list(directory)
-            directory.close()
-            names = [entry[0] for entry in entries]
-            self.assertEqual(set(names), set([child_dir, child_file]))
-            check_entry(entries, child_dir)
-            check_entry(entries, child_file)
-
-        return deferred.addCallback(check_open_directory)
+        check_entry(entries, child_dir)
+        check_entry(entries, child_file)
 
+    @defer.inlineCallbacks
     def test_openDirectoryError(self):
         # Errors in openDirectory are translated into SFTPErrors.
         nonexistent = self.getPathSegment()
-        deferred = self.sftp_server.openDirectory(nonexistent)
-        return assert_fails_with(deferred, filetransfer.SFTPError)
+        with ExpectedException(filetransfer.SFTPError):
+            yield self.sftp_server.openDirectory(nonexistent)
 
+    @defer.inlineCallbacks
     def test_openDirectoryMemory(self):
         """openDirectory works on MemoryTransport."""
         transport = MemoryTransport()
         transport.put_bytes('hello', b'hello')
         sftp_server = TransportSFTPServer(AsyncTransport(transport))
-        deferred = sftp_server.openDirectory('.')
-
-        def check_directory(directory):
-            with closing(directory):
-                names = [entry[0] for entry in directory]
-            self.assertEqual(['hello'], names)
-
-        return deferred.addCallback(check_directory)
+        directory = yield sftp_server.openDirectory('.')
+        with closing(directory):
+            names = [entry[0] for entry in directory]
+        self.assertEqual(['hello'], names)
 
     def test__format_directory_entries_with_MemoryStat(self):
         """format_directory_entries works with MemoryStat.