1# Copyright (C) 2011  Jeff Forcier <jeff@bitprophet.org>
2#
3# This file is part of ssh.
4#
5# 'ssh' is free software; you can redistribute it and/or modify it under the
6# terms of the GNU Lesser General Public License as published by the Free
7# Software Foundation; either version 2.1 of the License, or (at your option)
8# any later version.
9#
10# 'ssh' is distrubuted in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
13# details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with 'ssh'; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA.
18
19"""
20some unit tests to make sure sftp works.
21
22a real actual sftp server is contacted, and a new folder is created there to
23do test file operations in (so no existing files will be harmed).
24"""
25
26from binascii import hexlify
27import logging
28import os
29import random
30import struct
31import sys
32import threading
33import time
34import unittest
35
36import ssh
37from stub_sftp import StubServer, StubSFTPServer
38from loop import LoopSocket
39from ssh.sftp_attr import SFTPAttributes
40
41ARTICLE = '''
42Insulin sensitivity and liver insulin receptor structure in ducks from two
43genera
44
45T. Constans, B. Chevalier, M. Derouet and J. Simon
46Station de Recherches Avicoles, Institut National de la Recherche Agronomique,
47Nouzilly, France.
48
49Insulin sensitivity and liver insulin receptor structure were studied in
505-wk-old ducks from two genera (Muscovy and Pekin). In the fasting state, both
51duck types were equally resistant to exogenous insulin compared with chicken.
52Despite the low potency of duck insulin, the number of insulin receptors was
53lower in Muscovy duck and similar in Pekin duck and chicken liver membranes.
54After 125I-insulin cross-linking, the size of the alpha-subunit of the
55receptors from the three species was 135,000. Wheat germ agglutinin-purified
56receptors from the three species were contaminated by an active and unusual
57adenosinetriphosphatase (ATPase) contaminant (highest activity in Muscovy
58duck). Sequential purification of solubilized receptor from both duck types on
59lentil and then wheat germ agglutinin lectins led to a fraction of receptors
60very poor in ATPase activity that exhibited a beta-subunit size (95,000) and
61tyrosine kinase activity similar to those of ATPase-free chicken insulin
62receptors. Therefore the ducks from the two genera exhibit an alpha-beta-
63structure for liver insulin receptors and a clear difference in the number of
64liver insulin receptors. Their sensitivity to insulin is, however, similarly
65decreased compared with chicken.
66'''
67
68FOLDER = os.environ.get('TEST_FOLDER', 'temp-testing000')
69
70sftp = None
71tc = None
72g_big_file_test = True
73
74
75def get_sftp():
76    global sftp
77    return sftp
78
79
80class SFTPTest (unittest.TestCase):
81
82    def init(hostname, username, keyfile, passwd):
83        global sftp, tc
84
85        t = ssh.Transport(hostname)
86        tc = t
87        try:
88            key = ssh.RSAKey.from_private_key_file(keyfile, passwd)
89        except ssh.PasswordRequiredException:
90            sys.stderr.write('\n\nssh.RSAKey.from_private_key_file REQUIRES PASSWORD.\n')
91            sys.stderr.write('You have two options:\n')
92            sys.stderr.write('* Use the "-K" option to point to a different (non-password-protected)\n')
93            sys.stderr.write('  private key file.\n')
94            sys.stderr.write('* Use the "-P" option to provide the password needed to unlock this private\n')
95            sys.stderr.write('  key.\n')
96            sys.stderr.write('\n')
97            sys.exit(1)
98        try:
99            t.connect(username=username, pkey=key)
100        except ssh.SSHException:
101            t.close()
102            sys.stderr.write('\n\nssh.Transport.connect FAILED.\n')
103            sys.stderr.write('There are several possible reasons why it might fail so quickly:\n\n')
104            sys.stderr.write('* The host to connect to (%s) is not a valid SSH server.\n' % hostname)
105            sys.stderr.write('  (Use the "-H" option to change the host.)\n')
106            sys.stderr.write('* The username to auth as (%s) is invalid.\n' % username)
107            sys.stderr.write('  (Use the "-U" option to change the username.)\n')
108            sys.stderr.write('* The private key given (%s) is not accepted by the server.\n' % keyfile)
109            sys.stderr.write('  (Use the "-K" option to provide a different key file.)\n')
110            sys.stderr.write('\n')
111            sys.exit(1)
112        sftp = ssh.SFTP.from_transport(t)
113    init = staticmethod(init)
114
115    def init_loopback():
116        global sftp, tc
117
118        socks = LoopSocket()
119        sockc = LoopSocket()
120        sockc.link(socks)
121        tc = ssh.Transport(sockc)
122        ts = ssh.Transport(socks)
123
124        host_key = ssh.RSAKey.from_private_key_file('tests/test_rsa.key')
125        ts.add_server_key(host_key)
126        event = threading.Event()
127        server = StubServer()
128        ts.set_subsystem_handler('sftp', ssh.SFTPServer, StubSFTPServer)
129        ts.start_server(event, server)
130        tc.connect(username='slowdive', password='pygmalion')
131        event.wait(1.0)
132
133        sftp = ssh.SFTP.from_transport(tc)
134    init_loopback = staticmethod(init_loopback)
135
136    def set_big_file_test(onoff):
137        global g_big_file_test
138        g_big_file_test = onoff
139    set_big_file_test = staticmethod(set_big_file_test)
140
141    def setUp(self):
142        global FOLDER
143        for i in xrange(1000):
144            FOLDER = FOLDER[:-3] + '%03d' % i
145            try:
146                sftp.mkdir(FOLDER)
147                break
148            except (IOError, OSError):
149                pass
150
151    def tearDown(self):
152        sftp.rmdir(FOLDER)
153
154    def test_1_file(self):
155        """
156        verify that we can create a file.
157        """
158        f = sftp.open(FOLDER + '/test', 'w')
159        try:
160            self.assertEqual(f.stat().st_size, 0)
161            f.close()
162        finally:
163            sftp.remove(FOLDER + '/test')
164
165    def test_2_close(self):
166        """
167        verify that closing the sftp session doesn't do anything bad, and that
168        a new one can be opened.
169        """
170        global sftp
171        sftp.close()
172        try:
173            sftp.open(FOLDER + '/test2', 'w')
174            self.fail('expected exception')
175        except:
176            pass
177        sftp = ssh.SFTP.from_transport(tc)
178
179    def test_3_write(self):
180        """
181        verify that a file can be created and written, and the size is correct.
182        """
183        f = sftp.open(FOLDER + '/duck.txt', 'w')
184        try:
185            f.write(ARTICLE)
186            f.close()
187            self.assertEqual(sftp.stat(FOLDER + '/duck.txt').st_size, 1483)
188        finally:
189            sftp.remove(FOLDER + '/duck.txt')
190
191    def test_4_append(self):
192        """
193        verify that a file can be opened for append, and tell() still works.
194        """
195        f = sftp.open(FOLDER + '/append.txt', 'w')
196        try:
197            f.write('first line\nsecond line\n')
198            self.assertEqual(f.tell(), 23)
199            f.close()
200
201            f = sftp.open(FOLDER + '/append.txt', 'a+')
202            f.write('third line!!!\n')
203            self.assertEqual(f.tell(), 37)
204            self.assertEqual(f.stat().st_size, 37)
205            f.seek(-26, f.SEEK_CUR)
206            self.assertEqual(f.readline(), 'second line\n')
207            f.close()
208        finally:
209            sftp.remove(FOLDER + '/append.txt')
210
211    def test_5_rename(self):
212        """
213        verify that renaming a file works.
214        """
215        f = sftp.open(FOLDER + '/first.txt', 'w')
216        try:
217            f.write('content!\n');
218            f.close()
219            sftp.rename(FOLDER + '/first.txt', FOLDER + '/second.txt')
220            try:
221                f = sftp.open(FOLDER + '/first.txt', 'r')
222                self.assert_(False, 'no exception on reading nonexistent file')
223            except IOError:
224                pass
225            f = sftp.open(FOLDER + '/second.txt', 'r')
226            f.seek(-6, f.SEEK_END)
227            self.assertEqual(f.read(4), 'tent')
228            f.close()
229        finally:
230            try:
231                sftp.remove(FOLDER + '/first.txt')
232            except:
233                pass
234            try:
235                sftp.remove(FOLDER + '/second.txt')
236            except:
237                pass
238
239    def test_6_folder(self):
240        """
241        create a temporary folder, verify that we can create a file in it, then
242        remove the folder and verify that we can't create a file in it anymore.
243        """
244        sftp.mkdir(FOLDER + '/subfolder')
245        f = sftp.open(FOLDER + '/subfolder/test', 'w')
246        f.close()
247        sftp.remove(FOLDER + '/subfolder/test')
248        sftp.rmdir(FOLDER + '/subfolder')
249        try:
250            f = sftp.open(FOLDER + '/subfolder/test')
251            # shouldn't be able to create that file
252            self.assert_(False, 'no exception at dummy file creation')
253        except IOError:
254            pass
255
256    def test_7_listdir(self):
257        """
258        verify that a folder can be created, a bunch of files can be placed in it,
259        and those files show up in sftp.listdir.
260        """
261        try:
262            f = sftp.open(FOLDER + '/duck.txt', 'w')
263            f.close()
264
265            f = sftp.open(FOLDER + '/fish.txt', 'w')
266            f.close()
267
268            f = sftp.open(FOLDER + '/tertiary.py', 'w')
269            f.close()
270
271            x = sftp.listdir(FOLDER)
272            self.assertEqual(len(x), 3)
273            self.assert_('duck.txt' in x)
274            self.assert_('fish.txt' in x)
275            self.assert_('tertiary.py' in x)
276            self.assert_('random' not in x)
277        finally:
278            sftp.remove(FOLDER + '/duck.txt')
279            sftp.remove(FOLDER + '/fish.txt')
280            sftp.remove(FOLDER + '/tertiary.py')
281
282    def test_8_setstat(self):
283        """
284        verify that the setstat functions (chown, chmod, utime, truncate) work.
285        """
286        f = sftp.open(FOLDER + '/special', 'w')
287        try:
288            f.write('x' * 1024)
289            f.close()
290
291            stat = sftp.stat(FOLDER + '/special')
292            sftp.chmod(FOLDER + '/special', (stat.st_mode & ~0777) | 0600)
293            stat = sftp.stat(FOLDER + '/special')
294            expected_mode = 0600
295            if sys.platform == 'win32':
296                # chmod not really functional on windows
297                expected_mode = 0666
298            if sys.platform == 'cygwin':
299                # even worse.
300                expected_mode = 0644
301            self.assertEqual(stat.st_mode & 0777, expected_mode)
302            self.assertEqual(stat.st_size, 1024)
303
304            mtime = stat.st_mtime - 3600
305            atime = stat.st_atime - 1800
306            sftp.utime(FOLDER + '/special', (atime, mtime))
307            stat = sftp.stat(FOLDER + '/special')
308            self.assertEqual(stat.st_mtime, mtime)
309            if sys.platform not in ('win32', 'cygwin'):
310                self.assertEqual(stat.st_atime, atime)
311
312            # can't really test chown, since we'd have to know a valid uid.
313
314            sftp.truncate(FOLDER + '/special', 512)
315            stat = sftp.stat(FOLDER + '/special')
316            self.assertEqual(stat.st_size, 512)
317        finally:
318            sftp.remove(FOLDER + '/special')
319
320    def test_9_fsetstat(self):
321        """
322        verify that the fsetstat functions (chown, chmod, utime, truncate)
323        work on open files.
324        """
325        f = sftp.open(FOLDER + '/special', 'w')
326        try:
327            f.write('x' * 1024)
328            f.close()
329
330            f = sftp.open(FOLDER + '/special', 'r+')
331            stat = f.stat()
332            f.chmod((stat.st_mode & ~0777) | 0600)
333            stat = f.stat()
334
335            expected_mode = 0600
336            if sys.platform == 'win32':
337                # chmod not really functional on windows
338                expected_mode = 0666
339            if sys.platform == 'cygwin':
340                # even worse.
341                expected_mode = 0644
342            self.assertEqual(stat.st_mode & 0777, expected_mode)
343            self.assertEqual(stat.st_size, 1024)
344
345            mtime = stat.st_mtime - 3600
346            atime = stat.st_atime - 1800
347            f.utime((atime, mtime))
348            stat = f.stat()
349            self.assertEqual(stat.st_mtime, mtime)
350            if sys.platform not in ('win32', 'cygwin'):
351                self.assertEqual(stat.st_atime, atime)
352
353            # can't really test chown, since we'd have to know a valid uid.
354
355            f.truncate(512)
356            stat = f.stat()
357            self.assertEqual(stat.st_size, 512)
358            f.close()
359        finally:
360            sftp.remove(FOLDER + '/special')
361
362    def test_A_readline_seek(self):
363        """
364        create a text file and write a bunch of text into it.  then count the lines
365        in the file, and seek around to retreive particular lines.  this should
366        verify that read buffering and 'tell' work well together, and that read
367        buffering is reset on 'seek'.
368        """
369        try:
370            f = sftp.open(FOLDER + '/duck.txt', 'w')
371            f.write(ARTICLE)
372            f.close()
373
374            f = sftp.open(FOLDER + '/duck.txt', 'r+')
375            line_number = 0
376            loc = 0
377            pos_list = []
378            for line in f:
379                line_number += 1
380                pos_list.append(loc)
381                loc = f.tell()
382            f.seek(pos_list[6], f.SEEK_SET)
383            self.assertEqual(f.readline(), 'Nouzilly, France.\n')
384            f.seek(pos_list[17], f.SEEK_SET)
385            self.assertEqual(f.readline()[:4], 'duck')
386            f.seek(pos_list[10], f.SEEK_SET)
387            self.assertEqual(f.readline(), 'duck types were equally resistant to exogenous insulin compared with chicken.\n')
388            f.close()
389        finally:
390            sftp.remove(FOLDER + '/duck.txt')
391
392    def test_B_write_seek(self):
393        """
394        create a text file, seek back and change part of it, and verify that the
395        changes worked.
396        """
397        f = sftp.open(FOLDER + '/testing.txt', 'w')
398        try:
399            f.write('hello kitty.\n')
400            f.seek(-5, f.SEEK_CUR)
401            f.write('dd')
402            f.close()
403
404            self.assertEqual(sftp.stat(FOLDER + '/testing.txt').st_size, 13)
405            f = sftp.open(FOLDER + '/testing.txt', 'r')
406            data = f.read(20)
407            f.close()
408            self.assertEqual(data, 'hello kiddy.\n')
409        finally:
410            sftp.remove(FOLDER + '/testing.txt')
411
412    def test_C_symlink(self):
413        """
414        create a symlink and then check that lstat doesn't follow it.
415        """
416        if not hasattr(os, "symlink"):
417            # skip symlink tests on windows
418            return
419
420        f = sftp.open(FOLDER + '/original.txt', 'w')
421        try:
422            f.write('original\n')
423            f.close()
424            sftp.symlink('original.txt', FOLDER + '/link.txt')
425            self.assertEqual(sftp.readlink(FOLDER + '/link.txt'), 'original.txt')
426
427            f = sftp.open(FOLDER + '/link.txt', 'r')
428            self.assertEqual(f.readlines(), [ 'original\n' ])
429            f.close()
430
431            cwd = sftp.normalize('.')
432            if cwd[-1] == '/':
433                cwd = cwd[:-1]
434            abs_path = cwd + '/' + FOLDER + '/original.txt'
435            sftp.symlink(abs_path, FOLDER + '/link2.txt')
436            self.assertEqual(abs_path, sftp.readlink(FOLDER + '/link2.txt'))
437
438            self.assertEqual(sftp.lstat(FOLDER + '/link.txt').st_size, 12)
439            self.assertEqual(sftp.stat(FOLDER + '/link.txt').st_size, 9)
440            # the sftp server may be hiding extra path members from us, so the
441            # length may be longer than we expect:
442            self.assert_(sftp.lstat(FOLDER + '/link2.txt').st_size >= len(abs_path))
443            self.assertEqual(sftp.stat(FOLDER + '/link2.txt').st_size, 9)
444            self.assertEqual(sftp.stat(FOLDER + '/original.txt').st_size, 9)
445        finally:
446            try:
447                sftp.remove(FOLDER + '/link.txt')
448            except:
449                pass
450            try:
451                sftp.remove(FOLDER + '/link2.txt')
452            except:
453                pass
454            try:
455                sftp.remove(FOLDER + '/original.txt')
456            except:
457                pass
458
459    def test_D_flush_seek(self):
460        """
461        verify that buffered writes are automatically flushed on seek.
462        """
463        f = sftp.open(FOLDER + '/happy.txt', 'w', 1)
464        try:
465            f.write('full line.\n')
466            f.write('partial')
467            f.seek(9, f.SEEK_SET)
468            f.write('?\n')
469            f.close()
470
471            f = sftp.open(FOLDER + '/happy.txt', 'r')
472            self.assertEqual(f.readline(), 'full line?\n')
473            self.assertEqual(f.read(7), 'partial')
474            f.close()
475        finally:
476            try:
477                sftp.remove(FOLDER + '/happy.txt')
478            except:
479                pass
480
481    def test_E_realpath(self):
482        """
483        test that realpath is returning something non-empty and not an
484        error.
485        """
486        pwd = sftp.normalize('.')
487        self.assert_(len(pwd) > 0)
488        f = sftp.normalize('./' + FOLDER)
489        self.assert_(len(f) > 0)
490        self.assertEquals(os.path.join(pwd, FOLDER), f)
491
492    def test_F_mkdir(self):
493        """
494        verify that mkdir/rmdir work.
495        """
496        try:
497            sftp.mkdir(FOLDER + '/subfolder')
498        except:
499            self.assert_(False, 'exception creating subfolder')
500        try:
501            sftp.mkdir(FOLDER + '/subfolder')
502            self.assert_(False, 'no exception overwriting subfolder')
503        except IOError:
504            pass
505        try:
506            sftp.rmdir(FOLDER + '/subfolder')
507        except:
508            self.assert_(False, 'exception removing subfolder')
509        try:
510            sftp.rmdir(FOLDER + '/subfolder')
511            self.assert_(False, 'no exception removing nonexistent subfolder')
512        except IOError:
513            pass
514
515    def test_G_chdir(self):
516        """
517        verify that chdir/getcwd work.
518        """
519        root = sftp.normalize('.')
520        if root[-1] != '/':
521            root += '/'
522        try:
523            sftp.mkdir(FOLDER + '/alpha')
524            sftp.chdir(FOLDER + '/alpha')
525            sftp.mkdir('beta')
526            self.assertEquals(root + FOLDER + '/alpha', sftp.getcwd())
527            self.assertEquals(['beta'], sftp.listdir('.'))
528
529            sftp.chdir('beta')
530            f = sftp.open('fish', 'w')
531            f.write('hello\n')
532            f.close()
533            sftp.chdir('..')
534            self.assertEquals(['fish'], sftp.listdir('beta'))
535            sftp.chdir('..')
536            self.assertEquals(['fish'], sftp.listdir('alpha/beta'))
537        finally:
538            sftp.chdir(root)
539            try:
540                sftp.unlink(FOLDER + '/alpha/beta/fish')
541            except:
542                pass
543            try:
544                sftp.rmdir(FOLDER + '/alpha/beta')
545            except:
546                pass
547            try:
548                sftp.rmdir(FOLDER + '/alpha')
549            except:
550                pass
551
552    def test_H_get_put(self):
553        """
554        verify that get/put work.
555        """
556        import os, warnings
557        warnings.filterwarnings('ignore', 'tempnam.*')
558
559        localname = os.tempnam()
560        text = 'All I wanted was a plastic bunny rabbit.\n'
561        f = open(localname, 'wb')
562        f.write(text)
563        f.close()
564        saved_progress = []
565        def progress_callback(x, y):
566            saved_progress.append((x, y))
567        sftp.put(localname, FOLDER + '/bunny.txt', progress_callback)
568
569        f = sftp.open(FOLDER + '/bunny.txt', 'r')
570        self.assertEquals(text, f.read(128))
571        f.close()
572        self.assertEquals((41, 41), saved_progress[-1])
573
574        os.unlink(localname)
575        localname = os.tempnam()
576        saved_progress = []
577        sftp.get(FOLDER + '/bunny.txt', localname, progress_callback)
578
579        f = open(localname, 'rb')
580        self.assertEquals(text, f.read(128))
581        f.close()
582        self.assertEquals((41, 41), saved_progress[-1])
583
584        os.unlink(localname)
585        sftp.unlink(FOLDER + '/bunny.txt')
586
587    def test_I_check(self):
588        """
589        verify that file.check() works against our own server.
590        (it's an sftp extension that we support, and may be the only ones who
591        support it.)
592        """
593        f = sftp.open(FOLDER + '/kitty.txt', 'w')
594        f.write('here kitty kitty' * 64)
595        f.close()
596
597        try:
598            f = sftp.open(FOLDER + '/kitty.txt', 'r')
599            sum = f.check('sha1')
600            self.assertEquals('91059CFC6615941378D413CB5ADAF4C5EB293402', hexlify(sum).upper())
601            sum = f.check('md5', 0, 512)
602            self.assertEquals('93DE4788FCA28D471516963A1FE3856A', hexlify(sum).upper())
603            sum = f.check('md5', 0, 0, 510)
604            self.assertEquals('EB3B45B8CD55A0707D99B177544A319F373183D241432BB2157AB9E46358C4AC90370B5CADE5D90336FC1716F90B36D6',
605                              hexlify(sum).upper())
606            f.close()
607        finally:
608            sftp.unlink(FOLDER + '/kitty.txt')
609
610    def test_J_x_flag(self):
611        """
612        verify that the 'x' flag works when opening a file.
613        """
614        f = sftp.open(FOLDER + '/unusual.txt', 'wx')
615        f.close()
616
617        try:
618            try:
619                f = sftp.open(FOLDER + '/unusual.txt', 'wx')
620                self.fail('expected exception')
621            except IOError, x:
622                pass
623        finally:
624            sftp.unlink(FOLDER + '/unusual.txt')
625
626    def test_K_utf8(self):
627        """
628        verify that unicode strings are encoded into utf8 correctly.
629        """
630        f = sftp.open(FOLDER + '/something', 'w')
631        f.write('okay')
632        f.close()
633
634        try:
635            sftp.rename(FOLDER + '/something', FOLDER + u'/\u00fcnic\u00f8de')
636            sftp.open(FOLDER + '/\xc3\xbcnic\xc3\xb8\x64\x65', 'r')
637        except Exception, e:
638            self.fail('exception ' + e)
639        sftp.unlink(FOLDER + '/\xc3\xbcnic\xc3\xb8\x64\x65')
640
641    def test_L_utf8_chdir(self):
642        sftp.mkdir(FOLDER + u'\u00fcnic\u00f8de')
643        try:
644            sftp.chdir(FOLDER + u'\u00fcnic\u00f8de')
645            f = sftp.open('something', 'w')
646            f.write('okay')
647            f.close()
648            sftp.unlink('something')
649        finally:
650            sftp.chdir(None)
651            sftp.rmdir(FOLDER + u'\u00fcnic\u00f8de')
652
653    def test_M_bad_readv(self):
654        """
655        verify that readv at the end of the file doesn't essplode.
656        """
657        f = sftp.open(FOLDER + '/zero', 'w')
658        f.close()
659        try:
660            f = sftp.open(FOLDER + '/zero', 'r')
661            data = f.readv([(0, 12)])
662            f.close()
663
664            f = sftp.open(FOLDER + '/zero', 'r')
665            f.prefetch()
666            data = f.read(100)
667            f.close()
668        finally:
669            sftp.unlink(FOLDER + '/zero')
670
671    def test_N_put_without_confirm(self):
672        """
673        verify that get/put work without confirmation.
674        """
675        import os, warnings
676        warnings.filterwarnings('ignore', 'tempnam.*')
677
678        localname = os.tempnam()
679        text = 'All I wanted was a plastic bunny rabbit.\n'
680        f = open(localname, 'wb')
681        f.write(text)
682        f.close()
683        saved_progress = []
684        def progress_callback(x, y):
685            saved_progress.append((x, y))
686        res = sftp.put(localname, FOLDER + '/bunny.txt', progress_callback, False)
687
688        self.assertEquals(SFTPAttributes().attr, res.attr)
689
690        f = sftp.open(FOLDER + '/bunny.txt', 'r')
691        self.assertEquals(text, f.read(128))
692        f.close()
693        self.assertEquals((41, 41), saved_progress[-1])
694
695        os.unlink(localname)
696        sftp.unlink(FOLDER + '/bunny.txt')
697
698    def XXX_test_M_seek_append(self):
699        """
700        verify that seek does't affect writes during append.
701
702        does not work except through ssh.  :(  openssh fails.
703        """
704        f = sftp.open(FOLDER + '/append.txt', 'a')
705        try:
706            f.write('first line\nsecond line\n')
707            f.seek(11, f.SEEK_SET)
708            f.write('third line\n')
709            f.close()
710
711            f = sftp.open(FOLDER + '/append.txt', 'r')
712            self.assertEqual(f.stat().st_size, 34)
713            self.assertEqual(f.readline(), 'first line\n')
714            self.assertEqual(f.readline(), 'second line\n')
715            self.assertEqual(f.readline(), 'third line\n')
716            f.close()
717        finally:
718            sftp.remove(FOLDER + '/append.txt')
719
720