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