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