1#! /usr/bin/env python 2# 3# Copyright 2009 Google Inc. All Rights Reserved. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Test that FakeFilesystem calls work identically to a real filesystem.""" 18 19#pylint: disable-all 20 21import os #@UnusedImport 22import os.path 23import shutil 24import sys 25import tempfile 26import time 27import sys 28if sys.version_info < (2, 7): 29 import unittest2 as unittest 30else: 31 import unittest 32 33import fake_filesystem 34 35 36def Sep(path): 37 """Converts slashes in the path to the architecture's path seperator.""" 38 if isinstance(path, str): 39 return path.replace('/', os.sep) 40 return path 41 42 43class TestCase(unittest.TestCase): 44 is_windows = sys.platform.startswith('win') 45 is_cygwin = sys.platform == 'cygwin' 46 _FAKE_FS_BASE = Sep('/fakefs') 47 48 49class FakeFilesystemVsRealTest(TestCase): 50 51 def _Paths(self, path): 52 """For a given path, return paths in the real and fake filesystems.""" 53 if not path: 54 return (None, None) 55 return (os.path.join(self.real_base, path), 56 os.path.join(self.fake_base, path)) 57 58 def _CreateTestFile(self, file_type, path, contents=None): 59 """Create a dir, file, or link in both the real fs and the fake.""" 60 path = Sep(path) 61 self._created_files.append([file_type, path, contents]) 62 real_path, fake_path = self._Paths(path) 63 if file_type == 'd': 64 os.mkdir(real_path) 65 self.fake_os.mkdir(fake_path) 66 if file_type == 'f': 67 fh = open(real_path, 'w') 68 fh.write(contents or '') 69 fh.close() 70 fh = self.fake_open(fake_path, 'w') 71 fh.write(contents or '') 72 fh.close() 73 # b for binary file 74 if file_type == 'b': 75 fh = open(real_path, 'wb') 76 fh.write(contents or '') 77 fh.close() 78 fh = self.fake_open(fake_path, 'wb') 79 fh.write(contents or '') 80 fh.close() 81 # l for symlink, h for hard link 82 if file_type in ('l', 'h'): 83 real_target, fake_target = (contents, contents) 84 # If it begins with '/', make it relative to the base. You can't go 85 # creating files in / for the real file system. 86 if contents.startswith(os.sep): 87 real_target, fake_target = self._Paths(contents[1:]) 88 if file_type == 'l': 89 os.symlink(real_target, real_path) 90 self.fake_os.symlink(fake_target, fake_path) 91 elif file_type == 'h': 92 os.link(real_target, real_path) 93 self.fake_os.link(fake_target, fake_path) 94 95 def setUp(self): 96 # Base paths in the real and test file systems. We keep them different 97 # so that missing features in the fake don't fall through to the base 98 # operations and magically succeed. 99 tsname = 'fakefs.%s' % time.time() 100 # Fully expand the base_path - required on OS X. 101 self.real_base = os.path.realpath( 102 os.path.join(tempfile.gettempdir(), tsname)) 103 os.chdir(tempfile.gettempdir()) 104 if os.path.isdir(self.real_base): 105 shutil.rmtree(self.real_base) 106 os.mkdir(self.real_base) 107 self.fake_base = self._FAKE_FS_BASE 108 109 # Make sure we can write to the physical testing temp directory. 110 self.assertTrue(os.access(self.real_base, os.W_OK)) 111 112 self.fake_filesystem = fake_filesystem.FakeFilesystem() 113 self.fake_filesystem.CreateDirectory(self.fake_base) 114 self.fake_os = fake_filesystem.FakeOsModule(self.fake_filesystem) 115 self.fake_open = fake_filesystem.FakeFileOpen(self.fake_filesystem) 116 self._created_files = [] 117 118 os.chdir(self.real_base) 119 self.fake_os.chdir(self.fake_base) 120 121 def tearDown(self): 122 # We have to remove all the files from the real FS. Doing the same for the 123 # fake FS is optional, but doing it is an extra sanity check. 124 os.chdir(tempfile.gettempdir()) 125 try: 126 rev_files = self._created_files[:] 127 rev_files.reverse() 128 for info in rev_files: 129 real_path, fake_path = self._Paths(info[1]) 130 if info[0] == 'd': 131 try: 132 os.rmdir(real_path) 133 except OSError as e: 134 if 'Directory not empty' in e: 135 self.fail('Real path %s not empty: %s : %s' % ( 136 real_path, e, os.listdir(real_path))) 137 else: 138 raise 139 self.fake_os.rmdir(fake_path) 140 if info[0] == 'f' or info[0] == 'l': 141 os.remove(real_path) 142 self.fake_os.remove(fake_path) 143 finally: 144 shutil.rmtree(self.real_base) 145 146 def _GetErrno(self, raised_error): 147 try: 148 return (raised_error and raised_error.errno) or None 149 except AttributeError: 150 return None 151 152 def _CompareBehaviors(self, method_name, path, real, fake, 153 method_returns_path=False): 154 """Invoke an os method in both real and fake contexts and compare results. 155 156 Invoke a real filesystem method with a path to a real file and invoke a fake 157 filesystem method with a path to a fake file and compare the results. We 158 expect some calls to throw Exceptions, so we catch those and compare them. 159 160 Args: 161 method_name: Name of method being tested, for use in error messages. 162 path: potential path to a file in the real and fake file systems, passing 163 an empty tuple indicates that no arguments to pass to method. 164 real: built-in system library or method from the built-in system library 165 which takes a path as an arg and returns some value. 166 fake: fake_filsystem object or method from a fake_filesystem class 167 which takes a path as an arg and returns some value. 168 method_returns_path: True if the method returns a path, and thus we must 169 compensate for expected difference between real and fake. 170 171 Returns: 172 A description of the difference in behavior, or None. 173 """ 174 # pylint: disable=C6403 175 176 def _ErrorClass(e): 177 return (e and e.__class__.__name__) or 'None' 178 179 real_value = None 180 fake_value = None 181 real_err = None 182 fake_err = None 183 method_call = '%s' % method_name 184 method_call += '()' if path == () else '(%s)' % path 185 # Catching Exception below gives a lint warning, but it's what we need. 186 try: 187 args = [] if path == () else [path] 188 real_method = real 189 if not callable(real): 190 real_method = getattr(real, method_name) 191 real_value = str(real_method(*args)) 192 except Exception as e: # pylint: disable-msg=W0703 193 real_err = e 194 try: 195 fake_method = fake 196 if not callable(fake): 197 fake_method = getattr(fake, method_name) 198 args = [] if path == () else [path] 199 fake_value = str(fake_method(*args)) 200 except Exception as e: # pylint: disable-msg=W0703 201 fake_err = e 202 # We only compare on the error class because the acutal error contents 203 # is almost always different because of the file paths. 204 if _ErrorClass(real_err) != _ErrorClass(fake_err): 205 if real_err is None: 206 return '%s: real version returned %s, fake raised %s' % ( 207 method_call, real_value, _ErrorClass(fake_err)) 208 if fake_err is None: 209 return '%s: real version raised %s, fake returned %s' % ( 210 method_call, _ErrorClass(real_err), fake_value) 211 return '%s: real version raised %s, fake raised %s' % ( 212 method_call, _ErrorClass(real_err), _ErrorClass(fake_err)) 213 real_errno = self._GetErrno(real_err) 214 fake_errno = self._GetErrno(fake_err) 215 if real_errno != fake_errno: 216 return '%s(%s): both raised %s, real errno %s, fake errno %s' % ( 217 method_name, path, _ErrorClass(real_err), real_errno, fake_errno) 218 # If the method is supposed to return a full path AND both values 219 # begin with the expected full path, then trim it off. 220 if method_returns_path: 221 if (real_value and fake_value 222 and real_value.startswith(self.real_base) 223 and fake_value.startswith(self.fake_base)): 224 real_value = real_value[len(self.real_base):] 225 fake_value = fake_value[len(self.fake_base):] 226 if real_value != fake_value: 227 return '%s: real return %s, fake returned %s' % ( 228 method_call, real_value, fake_value) 229 return None 230 231 def assertOsMethodBehaviorMatches(self, method_name, path, 232 method_returns_path=False): 233 """Invoke an os method in both real and fake contexts and compare. 234 235 For a given method name (from the os module) and a path, compare the 236 behavior of the system provided module against the fake_filesytem module. 237 We expect results and/or Exceptions raised to be identical. 238 239 Args: 240 method_name: Name of method being tested. 241 path: potential path to a file in the real and fake file systems. 242 method_returns_path: True if the method returns a path, and thus we must 243 compensate for expected difference between real and fake. 244 245 Returns: 246 A description of the difference in behavior, or None. 247 """ 248 path = Sep(path) 249 return self._CompareBehaviors(method_name, path, os, self.fake_os, 250 method_returns_path) 251 252 def DiffOpenMethodBehavior(self, method_name, path, mode, data, 253 method_returns_data=True): 254 """Invoke an open method in both real and fkae contexts and compare. 255 256 Args: 257 method_name: Name of method being tested. 258 path: potential path to a file in the real and fake file systems. 259 mode: how to open the file. 260 data: any data to pass to the method. 261 method_returns_data: True if a method returns some sort of data. 262 263 For a given method name (from builtin open) and a path, compare the 264 behavior of the system provided module against the fake_filesytem module. 265 We expect results and/or Exceptions raised to be identical. 266 267 Returns: 268 A description of the difference in behavior, or None. 269 """ 270 with open(path, mode) as real_fh: 271 with self.fake_open(path, mode) as fake_fh: 272 return self._CompareBehaviors(method_name, data, real_fh, fake_fh, 273 method_returns_data) 274 275 def DiffOsPathMethodBehavior(self, method_name, path, 276 method_returns_path=False): 277 """Invoke an os.path method in both real and fake contexts and compare. 278 279 For a given method name (from the os.path module) and a path, compare the 280 behavior of the system provided module against the fake_filesytem module. 281 We expect results and/or Exceptions raised to be identical. 282 283 Args: 284 method_name: Name of method being tested. 285 path: potential path to a file in the real and fake file systems. 286 method_returns_path: True if the method returns a path, and thus we must 287 compensate for expected difference between real and fake. 288 289 Returns: 290 A description of the difference in behavior, or None. 291 """ 292 return self._CompareBehaviors(method_name, path, os.path, self.fake_os.path, 293 method_returns_path) 294 295 def assertOsPathMethodBehaviorMatches(self, method_name, path, 296 method_returns_path=False): 297 """Assert that an os.path behaves the same in both real and fake contexts. 298 299 Wraps DiffOsPathMethodBehavior, raising AssertionError if any differences 300 are reported. 301 302 Args: 303 method_name: Name of method being tested. 304 path: potential path to a file in the real and fake file systems. 305 method_returns_path: True if the method returns a path, and thus we must 306 compensate for expected difference between real and fake. 307 308 Raises: 309 AssertionError if there is any difference in behavior. 310 """ 311 path = Sep(path) 312 diff = self.DiffOsPathMethodBehavior(method_name, path, method_returns_path) 313 if diff: 314 self.fail(diff) 315 316 def assertAllOsBehaviorsMatch(self, path): 317 path = Sep(path) 318 os_method_names = [] if self.is_windows else ['readlink'] 319 os_method_names_no_args = ['getcwd'] 320 if sys.version_info < (3, 0): 321 os_method_names_no_args.append('getcwdu') 322 os_path_method_names = ['isabs', 323 'isdir', 324 'isfile', 325 'exists' 326 ] 327 if not self.is_windows: 328 os_path_method_names.append('islink') 329 os_path_method_names.append('lexists') 330 wrapped_methods = [['access', self._AccessReal, self._AccessFake], 331 ['stat.size', self._StatSizeReal, self._StatSizeFake], 332 ['lstat.size', self._LstatSizeReal, self._LstatSizeFake] 333 ] 334 335 differences = [] 336 for method_name in os_method_names: 337 diff = self.assertOsMethodBehaviorMatches(method_name, path) 338 if diff: 339 differences.append(diff) 340 for method_name in os_method_names_no_args: 341 diff = self.assertOsMethodBehaviorMatches(method_name, (), 342 method_returns_path=True) 343 if diff: 344 differences.append(diff) 345 for method_name in os_path_method_names: 346 diff = self.DiffOsPathMethodBehavior(method_name, path) 347 if diff: 348 differences.append(diff) 349 for m in wrapped_methods: 350 diff = self._CompareBehaviors(m[0], path, m[1], m[2]) 351 if diff: 352 differences.append(diff) 353 if differences: 354 self.fail('Behaviors do not match for %s:\n %s' % 355 (path, '\n '.join(differences))) 356 357 def assertFileHandleBehaviorsMatch(self, path, mode, data): 358 path = Sep(path) 359 write_method_names = ['write', 'writelines'] 360 read_method_names = ['read', 'readlines'] 361 other_method_names = ['truncate', 'flush', 'close'] 362 differences = [] 363 for method_name in write_method_names: 364 diff = self.DiffOpenMethodBehavior(method_name, path, mode, data) 365 if diff: 366 differences.append(diff) 367 for method_name in read_method_names + other_method_names: 368 diff = self.DiffOpenMethodBehavior(method_name, path, mode, ()) 369 if diff: 370 differences.append(diff) 371 if differences: 372 self.fail('Behaviors do not match for %s:\n %s' % 373 (path, '\n '.join(differences))) 374 375 # Helpers for checks which are not straight method calls. 376 377 def _AccessReal(self, path): 378 return os.access(path, os.R_OK) 379 380 def _AccessFake(self, path): 381 return self.fake_os.access(path, os.R_OK) 382 383 def _StatSizeReal(self, path): 384 real_path, unused_fake_path = self._Paths(path) 385 # fake_filesystem.py does not implement stat().st_size for directories 386 if os.path.isdir(real_path): 387 return None 388 return os.stat(real_path).st_size 389 390 def _StatSizeFake(self, path): 391 unused_real_path, fake_path = self._Paths(path) 392 # fake_filesystem.py does not implement stat().st_size for directories 393 if self.fake_os.path.isdir(fake_path): 394 return None 395 return self.fake_os.stat(fake_path).st_size 396 397 def _LstatSizeReal(self, path): 398 real_path, unused_fake_path = self._Paths(path) 399 if os.path.isdir(real_path): 400 return None 401 size = os.lstat(real_path).st_size 402 # Account for the difference in the lengths of the absolute paths. 403 if os.path.islink(real_path): 404 if os.readlink(real_path).startswith(os.sep): 405 size -= len(self.real_base) 406 return size 407 408 def _LstatSizeFake(self, path): 409 unused_real_path, fake_path = self._Paths(path) 410 #size = 0 411 if self.fake_os.path.isdir(fake_path): 412 return None 413 size = self.fake_os.lstat(fake_path).st_size 414 # Account for the difference in the lengths of the absolute paths. 415 if self.fake_os.path.islink(fake_path): 416 if self.fake_os.readlink(fake_path).startswith(os.sep): 417 size -= len(self.fake_base) 418 return size 419 420 def testIsabs(self): 421 # We do not have to create any files for isabs. 422 self.assertOsPathMethodBehaviorMatches('isabs', None) 423 self.assertOsPathMethodBehaviorMatches('isabs', '') 424 self.assertOsPathMethodBehaviorMatches('isabs', '/') 425 self.assertOsPathMethodBehaviorMatches('isabs', '/a') 426 self.assertOsPathMethodBehaviorMatches('isabs', 'a') 427 428 def testNonePath(self): 429 self.assertAllOsBehaviorsMatch(None) 430 431 def testEmptyPath(self): 432 self.assertAllOsBehaviorsMatch('') 433 434 def testRootPath(self): 435 self.assertAllOsBehaviorsMatch('/') 436 437 def testNonExistantFile(self): 438 self.assertAllOsBehaviorsMatch('foo') 439 440 def testEmptyFile(self): 441 self._CreateTestFile('f', 'aFile') 442 self.assertAllOsBehaviorsMatch('aFile') 443 444 def testFileWithContents(self): 445 self._CreateTestFile('f', 'aFile', 'some contents') 446 self.assertAllOsBehaviorsMatch('aFile') 447 448 def testFileWithBinaryContents(self): 449 self._CreateTestFile('b', 'aFile', b'some contents') 450 self.assertAllOsBehaviorsMatch('aFile') 451 452 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 453 def testSymLinkToEmptyFile(self): 454 self._CreateTestFile('f', 'aFile') 455 self._CreateTestFile('l', 'link_to_empty', 'aFile') 456 self.assertAllOsBehaviorsMatch('link_to_empty') 457 458 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 459 def TBD_testHardLinkToEmptyFile(self): 460 self._CreateTestFile('f', 'aFile') 461 self._CreateTestFile('h', 'link_to_empty', 'aFile') 462 self.assertAllOsBehaviorsMatch('link_to_empty') 463 464 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 465 def testSymLinkToRealFile(self): 466 self._CreateTestFile('f', 'aFile', 'some contents') 467 self._CreateTestFile('l', 'link_to_file', 'aFile') 468 self.assertAllOsBehaviorsMatch('link_to_file') 469 470 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 471 def TBD_testHardLinkToRealFile(self): 472 self._CreateTestFile('f', 'aFile', 'some contents') 473 self._CreateTestFile('h', 'link_to_file', 'aFile') 474 self.assertAllOsBehaviorsMatch('link_to_file') 475 476 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 477 def testBrokenSymLink(self): 478 self._CreateTestFile('l', 'broken_link', 'broken') 479 self._CreateTestFile('l', 'loop', '/a/loop') 480 self.assertAllOsBehaviorsMatch('broken_link') 481 482 def testFileInAFolder(self): 483 self._CreateTestFile('d', 'a') 484 self._CreateTestFile('d', 'a/b') 485 self._CreateTestFile('f', 'a/b/file', 'contents') 486 self.assertAllOsBehaviorsMatch('a/b/file') 487 488 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 489 def testAbsoluteSymLinkToFolder(self): 490 self._CreateTestFile('d', 'a') 491 self._CreateTestFile('d', 'a/b') 492 self._CreateTestFile('f', 'a/b/file', 'contents') 493 self._CreateTestFile('l', 'a/link', '/a/b') 494 self.assertAllOsBehaviorsMatch('a/link/file') 495 496 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 497 def testLinkToFolderAfterChdir(self): 498 self._CreateTestFile('d', 'a') 499 self._CreateTestFile('d', 'a/b') 500 self._CreateTestFile('f', 'a/b/file', 'contents') 501 self._CreateTestFile('l', 'a/link', '/a/b') 502 503 real_dir, fake_dir = self._Paths('a/b') 504 os.chdir(real_dir) 505 self.fake_os.chdir(fake_dir) 506 self.assertAllOsBehaviorsMatch('file') 507 508 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 509 def testRelativeSymLinkToFolder(self): 510 self._CreateTestFile('d', 'a') 511 self._CreateTestFile('d', 'a/b') 512 self._CreateTestFile('f', 'a/b/file', 'contents') 513 self._CreateTestFile('l', 'a/link', 'b') 514 self.assertAllOsBehaviorsMatch('a/link/file') 515 516 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 517 def testSymLinkToParent(self): 518 # Soft links on HFS+ / OS X behave differently. 519 if os.uname()[0] != 'Darwin': 520 self._CreateTestFile('d', 'a') 521 self._CreateTestFile('d', 'a/b') 522 self._CreateTestFile('l', 'a/b/c', '..') 523 self.assertAllOsBehaviorsMatch('a/b/c') 524 525 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 526 def testPathThroughSymLinkToParent(self): 527 self._CreateTestFile('d', 'a') 528 self._CreateTestFile('f', 'a/target', 'contents') 529 self._CreateTestFile('d', 'a/b') 530 self._CreateTestFile('l', 'a/b/c', '..') 531 self.assertAllOsBehaviorsMatch('a/b/c/target') 532 533 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 534 def testSymLinkToSiblingDirectory(self): 535 self._CreateTestFile('d', 'a') 536 self._CreateTestFile('d', 'a/b') 537 self._CreateTestFile('d', 'a/sibling_of_b') 538 self._CreateTestFile('f', 'a/sibling_of_b/target', 'contents') 539 self._CreateTestFile('l', 'a/b/c', '../sibling_of_b') 540 self.assertAllOsBehaviorsMatch('a/b/c/target') 541 542 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 543 def testSymLinkToSiblingDirectoryNonExistantFile(self): 544 self._CreateTestFile('d', 'a') 545 self._CreateTestFile('d', 'a/b') 546 self._CreateTestFile('d', 'a/sibling_of_b') 547 self._CreateTestFile('f', 'a/sibling_of_b/target', 'contents') 548 self._CreateTestFile('l', 'a/b/c', '../sibling_of_b') 549 self.assertAllOsBehaviorsMatch('a/b/c/file_does_not_exist') 550 551 @unittest.skipIf(TestCase.is_windows, 'no symlink in Windows') 552 def testBrokenSymLinkToSiblingDirectory(self): 553 self._CreateTestFile('d', 'a') 554 self._CreateTestFile('d', 'a/b') 555 self._CreateTestFile('d', 'a/sibling_of_b') 556 self._CreateTestFile('f', 'a/sibling_of_b/target', 'contents') 557 self._CreateTestFile('l', 'a/b/c', '../broken_sibling_of_b') 558 self.assertAllOsBehaviorsMatch('a/b/c/target') 559 560 def testRelativePath(self): 561 self._CreateTestFile('d', 'a') 562 self._CreateTestFile('d', 'a/b') 563 self._CreateTestFile('d', 'a/sibling_of_b') 564 self._CreateTestFile('f', 'a/sibling_of_b/target', 'contents') 565 self.assertAllOsBehaviorsMatch('a/b/../sibling_of_b/target') 566 567 def testBrokenRelativePath(self): 568 self._CreateTestFile('d', 'a') 569 self._CreateTestFile('d', 'a/b') 570 self._CreateTestFile('d', 'a/sibling_of_b') 571 self._CreateTestFile('f', 'a/sibling_of_b/target', 'contents') 572 self.assertAllOsBehaviorsMatch('a/b/../broken/target') 573 574 def testBadRelativePath(self): 575 self._CreateTestFile('d', 'a') 576 self._CreateTestFile('f', 'a/target', 'contents') 577 self._CreateTestFile('d', 'a/b') 578 self._CreateTestFile('d', 'a/sibling_of_b') 579 self._CreateTestFile('f', 'a/sibling_of_b/target', 'contents') 580 self.assertAllOsBehaviorsMatch('a/b/../broken/../target') 581 582 def testGetmtimeNonexistantPath(self): 583 self.assertOsPathMethodBehaviorMatches('getmtime', 'no/such/path') 584 585 def testBuiltinOpenModes(self): 586 self._CreateTestFile('f', 'read', 'some contents') 587 self._CreateTestFile('f', 'write', 'some contents') 588 self._CreateTestFile('f', 'append', 'some contents') 589 self.assertFileHandleBehaviorsMatch('read', 'r', 'other contents') 590 self.assertFileHandleBehaviorsMatch('write', 'w', 'other contents') 591 self.assertFileHandleBehaviorsMatch('append', 'a', 'other contents') 592 self._CreateTestFile('f', 'readplus', 'some contents') 593 self._CreateTestFile('f', 'writeplus', 'some contents') 594 self.assertFileHandleBehaviorsMatch('readplus', 'r+', 'other contents') 595 self.assertFileHandleBehaviorsMatch('writeplus', 'w+', 'other contents') 596 self._CreateTestFile('b', 'binaryread', b'some contents') 597 self._CreateTestFile('b', 'binarywrite', b'some contents') 598 self._CreateTestFile('b', 'binaryappend', b'some contents') 599 self.assertFileHandleBehaviorsMatch('binaryread', 'rb', b'other contents') 600 self.assertFileHandleBehaviorsMatch('binarywrite', 'wb', b'other contents') 601 self.assertFileHandleBehaviorsMatch('binaryappend', 'ab', b'other contents') 602 self.assertFileHandleBehaviorsMatch('read', 'rb', 'other contents') 603 self.assertFileHandleBehaviorsMatch('write', 'wb', 'other contents') 604 self.assertFileHandleBehaviorsMatch('append', 'ab', 'other contents') 605 606 607def main(unused_argv): 608 unittest.main() 609 610 611if __name__ == '__main__': 612 unittest.main() 613