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# pylint: disable-msg=W0612,W0613,C6409 16 17"""A fake filesystem implementation for unit testing. 18 19Includes: 20 FakeFile: Provides the appearance of a real file. 21 FakeDirectory: Provides the appearance of a real dir. 22 FakeFilesystem: Provides the appearance of a real directory hierarchy. 23 FakeOsModule: Uses FakeFilesystem to provide a fake os module replacement. 24 FakePathModule: Faked os.path module replacement. 25 FakeFileOpen: Faked file() and open() function replacements. 26 27Usage: 28>>> from pyfakefs import fake_filesystem 29>>> filesystem = fake_filesystem.FakeFilesystem() 30>>> os_module = fake_filesystem.FakeOsModule(filesystem) 31>>> pathname = '/a/new/dir/new-file' 32 33Create a new file object, creating parent directory objects as needed: 34>>> os_module.path.exists(pathname) 35False 36>>> new_file = filesystem.CreateFile(pathname) 37 38File objects can't be overwritten: 39>>> os_module.path.exists(pathname) 40True 41>>> try: 42... filesystem.CreateFile(pathname) 43... except IOError as e: 44... assert e.errno == errno.EEXIST, 'unexpected errno: %d' % e.errno 45... assert e.strerror == 'File already exists in fake filesystem' 46 47Remove a file object: 48>>> filesystem.RemoveObject(pathname) 49>>> os_module.path.exists(pathname) 50False 51 52Create a new file object at the previous path: 53>>> beatles_file = filesystem.CreateFile(pathname, 54... contents='Dear Prudence\\nWon\\'t you come out to play?\\n') 55>>> os_module.path.exists(pathname) 56True 57 58Use the FakeFileOpen class to read fake file objects: 59>>> file_module = fake_filesystem.FakeFileOpen(filesystem) 60>>> for line in file_module(pathname): 61... print line.rstrip() 62... 63Dear Prudence 64Won't you come out to play? 65 66File objects cannot be treated like directory objects: 67>>> os_module.listdir(pathname) #doctest: +NORMALIZE_WHITESPACE 68Traceback (most recent call last): 69 File "fake_filesystem.py", line 291, in listdir 70 raise OSError(errno.ENOTDIR, 71OSError: [Errno 20] Fake os module: not a directory: '/a/new/dir/new-file' 72 73The FakeOsModule can list fake directory objects: 74>>> os_module.listdir(os_module.path.dirname(pathname)) 75['new-file'] 76 77The FakeOsModule also supports stat operations: 78>>> import stat 79>>> stat.S_ISREG(os_module.stat(pathname).st_mode) 80True 81>>> stat.S_ISDIR(os_module.stat(os_module.path.dirname(pathname)).st_mode) 82True 83""" 84 85import errno 86import heapq 87import os 88import stat 89import sys 90import time 91import warnings 92import binascii 93 94try: 95 import cStringIO as io # pylint: disable-msg=C6204 96except ImportError: 97 import io # pylint: disable-msg=C6204 98 99__pychecker__ = 'no-reimportself' 100 101__version__ = '2.7' 102 103PERM_READ = 0o400 # Read permission bit. 104PERM_WRITE = 0o200 # Write permission bit. 105PERM_EXE = 0o100 # Write permission bit. 106PERM_DEF = 0o777 # Default permission bits. 107PERM_DEF_FILE = 0o666 # Default permission bits (regular file) 108PERM_ALL = 0o7777 # All permission bits. 109 110_OPEN_MODE_MAP = { 111 # mode name:(file must exist, need read, need write, 112 # truncate [implies need write], append) 113 'r': (True, True, False, False, False), 114 'w': (False, False, True, True, False), 115 'a': (False, False, True, False, True), 116 'r+': (True, True, True, False, False), 117 'w+': (False, True, True, True, False), 118 'a+': (False, True, True, False, True), 119 } 120 121_MAX_LINK_DEPTH = 20 122 123FAKE_PATH_MODULE_DEPRECATION = ('Do not instantiate a FakePathModule directly; ' 124 'let FakeOsModule instantiate it. See the ' 125 'FakeOsModule docstring for details.') 126 127 128class Error(Exception): 129 pass 130 131_is_windows = sys.platform.startswith('win') 132_is_cygwin = sys.platform == 'cygwin' 133 134if _is_windows: 135 # On Windows, raise WindowsError instead of OSError if available 136 OSError = WindowsError # pylint: disable-msg=E0602,W0622 137 138 139class FakeLargeFileIoException(Error): 140 def __init__(self, file_path): 141 Error.__init__(self, 142 'Read and write operations not supported for ' 143 'fake large file: %s' % file_path) 144 145 146def CopyModule(old): 147 """Recompiles and creates new module object.""" 148 saved = sys.modules.pop(old.__name__, None) 149 new = __import__(old.__name__) 150 sys.modules[old.__name__] = saved 151 return new 152 153 154class Hexlified(object): 155 """Wraps binary data in non-binary string""" 156 def __init__(self, contents): 157 self.contents = binascii.hexlify(contents).decode('utf-8') 158 159 def __len__(self): 160 return len(self.contents)//2 161 162 def recover(self, binary): 163 if binary: 164 return binascii.unhexlify(bytearray(self.contents, 'utf-8')) 165 else: 166 return binascii.unhexlify(bytearray(self.contents, 'utf-8')).decode(sys.getdefaultencoding()) 167 168 169class FakeFile(object): 170 """Provides the appearance of a real file. 171 172 Attributes currently faked out: 173 st_mode: user-specified, otherwise S_IFREG 174 st_ctime: the time.time() timestamp when the file is created. 175 st_size: the size of the file 176 177 Other attributes needed by os.stat are assigned default value of None 178 these include: st_ino, st_dev, st_nlink, st_uid, st_gid, st_atime, 179 st_mtime 180 """ 181 182 def __init__(self, name, st_mode=stat.S_IFREG | PERM_DEF_FILE, 183 contents=None): 184 """init. 185 186 Args: 187 name: name of the file/directory, without parent path information 188 st_mode: the stat.S_IF* constant representing the file type (i.e. 189 stat.S_IFREG, stat.SIFDIR) 190 contents: the contents of the filesystem object; should be a string for 191 regular files, and a list of other FakeFile or FakeDirectory objects 192 for FakeDirectory objects 193 """ 194 self.name = name 195 self.st_mode = st_mode 196 self.contents = contents 197 self.epoch = 0 198 self.st_ctime = int(time.time()) 199 self.st_atime = self.st_ctime 200 self.st_mtime = self.st_ctime 201 if contents: 202 self.st_size = len(contents) 203 else: 204 self.st_size = 0 205 # Non faked features, write setter methods for fakeing them 206 self.st_ino = None 207 self.st_dev = None 208 self.st_nlink = None 209 self.st_uid = None 210 self.st_gid = None 211 212 def SetLargeFileSize(self, st_size): 213 """Sets the self.st_size attribute and replaces self.content with None. 214 215 Provided specifically to simulate very large files without regards 216 to their content (which wouldn't fit in memory) 217 218 Args: 219 st_size: The desired file size 220 221 Raises: 222 IOError: if the st_size is not a non-negative integer 223 """ 224 # the st_size should be an positive integer value 225 if not isinstance(st_size, int) or st_size < 0: 226 raise IOError(errno.ENOSPC, 227 'Fake file object: can not create non negative integer ' 228 'size=%r fake file' % st_size, 229 self.name) 230 231 self.st_size = st_size 232 self.contents = None 233 234 def IsLargeFile(self): 235 """Return True if this file was initialized with size but no contents.""" 236 return self.contents is None 237 238 def SetContents(self, contents): 239 """Sets the file contents and size. 240 241 Args: 242 contents: string, new content of file. 243 """ 244 # Wrap byte arrays into a safe format 245 if sys.version_info >= (3, 0) and isinstance(contents, bytes): 246 contents = Hexlified(contents) 247 248 self.st_size = len(contents) 249 self.contents = contents 250 self.epoch += 1 251 252 def SetSize(self, st_size): 253 """Resizes file content, padding with nulls if new size exceeds the old. 254 255 Args: 256 st_size: The desired size for the file. 257 258 Raises: 259 IOError: if the st_size arg is not a non-negative integer 260 """ 261 262 if not isinstance(st_size, int) or st_size < 0: 263 raise IOError(errno.ENOSPC, 264 'Fake file object: can not create non negative integer ' 265 'size=%r fake file' % st_size, 266 self.name) 267 268 current_size = len(self.contents) 269 if st_size < current_size: 270 self.contents = self.contents[:st_size] 271 else: 272 self.contents = '%s%s' % (self.contents, '\0' * (st_size - current_size)) 273 self.st_size = len(self.contents) 274 self.epoch += 1 275 276 def SetATime(self, st_atime): 277 """Set the self.st_atime attribute. 278 279 Args: 280 st_atime: The desired atime. 281 """ 282 self.st_atime = st_atime 283 284 def SetMTime(self, st_mtime): 285 """Set the self.st_mtime attribute. 286 287 Args: 288 st_mtime: The desired mtime. 289 """ 290 self.st_mtime = st_mtime 291 292 def __str__(self): 293 return '%s(%o)' % (self.name, self.st_mode) 294 295 def SetIno(self, st_ino): 296 """Set the self.st_ino attribute. 297 298 Args: 299 st_ino: The desired inode. 300 """ 301 self.st_ino = st_ino 302 303 304class FakeDirectory(FakeFile): 305 """Provides the appearance of a real dir.""" 306 307 def __init__(self, name, perm_bits=PERM_DEF): 308 """init. 309 310 Args: 311 name: name of the file/directory, without parent path information 312 perm_bits: permission bits. defaults to 0o777. 313 """ 314 FakeFile.__init__(self, name, stat.S_IFDIR | perm_bits, {}) 315 316 def AddEntry(self, pathname): 317 """Adds a child FakeFile to this directory. 318 319 Args: 320 pathname: FakeFile instance to add as a child of this directory 321 """ 322 self.contents[pathname.name] = pathname 323 324 def GetEntry(self, pathname_name): 325 """Retrieves the specified child file or directory. 326 327 Args: 328 pathname_name: basename of the child object to retrieve 329 Returns: 330 string, file contents 331 Raises: 332 KeyError: if no child exists by the specified name 333 """ 334 return self.contents[pathname_name] 335 336 def RemoveEntry(self, pathname_name): 337 """Removes the specified child file or directory. 338 339 Args: 340 pathname_name: basename of the child object to remove 341 342 Raises: 343 KeyError: if no child exists by the specified name 344 """ 345 del self.contents[pathname_name] 346 347 def __str__(self): 348 rc = super(FakeDirectory, self).__str__() + ':\n' 349 for item in self.contents: 350 item_desc = self.contents[item].__str__() 351 for line in item_desc.split('\n'): 352 if line: 353 rc = rc + ' ' + line + '\n' 354 return rc 355 356 357class FakeFilesystem(object): 358 """Provides the appearance of a real directory tree for unit testing.""" 359 360 def __init__(self, path_separator=os.path.sep): 361 """init. 362 363 Args: 364 path_separator: optional substitute for os.path.sep 365 """ 366 self.path_separator = path_separator 367 self.root = FakeDirectory(self.path_separator) 368 self.cwd = self.root.name 369 # We can't query the current value without changing it: 370 self.umask = os.umask(0o22) 371 os.umask(self.umask) 372 # A list of open file objects. Their position in the list is their 373 # file descriptor number 374 self.open_files = [] 375 # A heap containing all free positions in self.open_files list 376 self.free_fd_heap = [] 377 378 def SetIno(self, path, st_ino): 379 """Set the self.st_ino attribute of file at 'path'. 380 381 Args: 382 path: Path to file. 383 st_ino: The desired inode. 384 """ 385 self.GetObject(path).SetIno(st_ino) 386 387 def AddOpenFile(self, file_obj): 388 """Adds file_obj to the list of open files on the filesystem. 389 390 The position in the self.open_files array is the file descriptor number 391 392 Args: 393 file_obj: file object to be added to open files list. 394 395 Returns: 396 File descriptor number for the file object. 397 """ 398 if self.free_fd_heap: 399 open_fd = heapq.heappop(self.free_fd_heap) 400 self.open_files[open_fd] = file_obj 401 return open_fd 402 403 self.open_files.append(file_obj) 404 return len(self.open_files) - 1 405 406 def CloseOpenFile(self, file_obj): 407 """Removes file_obj from the list of open files on the filesystem. 408 409 Sets the entry in open_files to None. 410 411 Args: 412 file_obj: file object to be removed to open files list. 413 """ 414 self.open_files[file_obj.filedes] = None 415 heapq.heappush(self.free_fd_heap, file_obj.filedes) 416 417 def GetOpenFile(self, file_des): 418 """Returns an open file. 419 420 Args: 421 file_des: file descriptor of the open file. 422 423 Raises: 424 OSError: an invalid file descriptor. 425 TypeError: filedes is not an integer. 426 427 Returns: 428 Open file object. 429 """ 430 if not isinstance(file_des, int): 431 raise TypeError('an integer is required') 432 if (file_des >= len(self.open_files) or 433 self.open_files[file_des] is None): 434 raise OSError(errno.EBADF, 'Bad file descriptor', file_des) 435 return self.open_files[file_des] 436 437 def CollapsePath(self, path): 438 """Mimics os.path.normpath using the specified path_separator. 439 440 Mimics os.path.normpath using the path_separator that was specified 441 for this FakeFilesystem. Normalizes the path, but unlike the method 442 NormalizePath, does not make it absolute. Eliminates dot components 443 (. and ..) and combines repeated path separators (//). Initial .. 444 components are left in place for relative paths. If the result is an empty 445 path, '.' is returned instead. Unlike the real os.path.normpath, this does 446 not replace '/' with '\\' on Windows. 447 448 Args: 449 path: (str) The path to normalize. 450 451 Returns: 452 (str) A copy of path with empty components and dot components removed. 453 """ 454 is_absolute_path = path.startswith(self.path_separator) 455 path_components = path.split(self.path_separator) 456 collapsed_path_components = [] 457 for component in path_components: 458 if (not component) or (component == '.'): 459 continue 460 if component == '..': 461 if collapsed_path_components and ( 462 collapsed_path_components[-1] != '..'): 463 # Remove an up-reference: directory/.. 464 collapsed_path_components.pop() 465 continue 466 elif is_absolute_path: 467 # Ignore leading .. components if starting from the root directory. 468 continue 469 collapsed_path_components.append(component) 470 collapsed_path = self.path_separator.join(collapsed_path_components) 471 if is_absolute_path: 472 collapsed_path = self.path_separator + collapsed_path 473 return collapsed_path or '.' 474 475 def NormalizePath(self, path): 476 """Absolutize and minimalize the given path. 477 478 Forces all relative paths to be absolute, and normalizes the path to 479 eliminate dot and empty components. 480 481 Args: 482 path: path to normalize 483 484 Returns: 485 The normalized path relative to the current working directory, or the root 486 directory if path is empty. 487 """ 488 if not path: 489 path = self.path_separator 490 elif not path.startswith(self.path_separator): 491 # Prefix relative paths with cwd, if cwd is not root. 492 path = self.path_separator.join( 493 (self.cwd != self.root.name and self.cwd or '', 494 path)) 495 if path == '.': 496 path = self.cwd 497 return self.CollapsePath(path) 498 499 def SplitPath(self, path): 500 """Mimics os.path.split using the specified path_separator. 501 502 Mimics os.path.split using the path_separator that was specified 503 for this FakeFilesystem. 504 505 Args: 506 path: (str) The path to split. 507 508 Returns: 509 (str) A duple (pathname, basename) for which pathname does not 510 end with a slash, and basename does not contain a slash. 511 """ 512 path_components = path.split(self.path_separator) 513 if not path_components: 514 return ('', '') 515 basename = path_components.pop() 516 if not path_components: 517 return ('', basename) 518 for component in path_components: 519 if component: 520 # The path is not the root; it contains a non-separator component. 521 # Strip all trailing separators. 522 while not path_components[-1]: 523 path_components.pop() 524 return (self.path_separator.join(path_components), basename) 525 # Root path. Collapse all leading separators. 526 return (self.path_separator, basename) 527 528 def JoinPaths(self, *paths): 529 """Mimics os.path.join using the specified path_separator. 530 531 Mimics os.path.join using the path_separator that was specified 532 for this FakeFilesystem. 533 534 Args: 535 *paths: (str) Zero or more paths to join. 536 537 Returns: 538 (str) The paths joined by the path separator, starting with the last 539 absolute path in paths. 540 """ 541 if len(paths) == 1: 542 return paths[0] 543 joined_path_segments = [] 544 for path_segment in paths: 545 if path_segment.startswith(self.path_separator): 546 # An absolute path 547 joined_path_segments = [path_segment] 548 else: 549 if (joined_path_segments and 550 not joined_path_segments[-1].endswith(self.path_separator)): 551 joined_path_segments.append(self.path_separator) 552 if path_segment: 553 joined_path_segments.append(path_segment) 554 return ''.join(joined_path_segments) 555 556 def GetPathComponents(self, path): 557 """Breaks the path into a list of component names. 558 559 Does not include the root directory as a component, as all paths 560 are considered relative to the root directory for the FakeFilesystem. 561 Callers should basically follow this pattern: 562 563 file_path = self.NormalizePath(file_path) 564 path_components = self.GetPathComponents(file_path) 565 current_dir = self.root 566 for component in path_components: 567 if component not in current_dir.contents: 568 raise IOError 569 DoStuffWithComponent(curent_dir, component) 570 current_dir = current_dir.GetEntry(component) 571 572 Args: 573 path: path to tokenize 574 575 Returns: 576 The list of names split from path 577 """ 578 if not path or path == self.root.name: 579 return [] 580 path_components = path.split(self.path_separator) 581 assert path_components 582 if not path_components[0]: 583 # This is an absolute path. 584 path_components = path_components[1:] 585 return path_components 586 587 def Exists(self, file_path): 588 """True if a path points to an existing file system object. 589 590 Args: 591 file_path: path to examine 592 593 Returns: 594 bool(if object exists) 595 596 Raises: 597 TypeError: if file_path is None 598 """ 599 if file_path is None: 600 raise TypeError 601 if not file_path: 602 return False 603 try: 604 file_path = self.ResolvePath(file_path) 605 except IOError: 606 return False 607 if file_path == self.root.name: 608 return True 609 path_components = self.GetPathComponents(file_path) 610 current_dir = self.root 611 for component in path_components: 612 if component not in current_dir.contents: 613 return False 614 current_dir = current_dir.contents[component] 615 return True 616 617 def ResolvePath(self, file_path): 618 """Follow a path, resolving symlinks. 619 620 ResolvePath traverses the filesystem along the specified file path, 621 resolving file names and symbolic links until all elements of the path are 622 exhausted, or we reach a file which does not exist. If all the elements 623 are not consumed, they just get appended to the path resolved so far. 624 This gives us the path which is as resolved as it can be, even if the file 625 does not exist. 626 627 This behavior mimics Unix semantics, and is best shown by example. Given a 628 file system that looks like this: 629 630 /a/b/ 631 /a/b/c -> /a/b2 c is a symlink to /a/b2 632 /a/b2/x 633 /a/c -> ../d 634 /a/x -> y 635 Then: 636 /a/b/x => /a/b/x 637 /a/c => /a/d 638 /a/x => /a/y 639 /a/b/c/d/e => /a/b2/d/e 640 641 Args: 642 file_path: path to examine 643 644 Returns: 645 resolved_path (string) or None 646 647 Raises: 648 TypeError: if file_path is None 649 IOError: if file_path is '' or a part of the path doesn't exist 650 """ 651 652 def _ComponentsToPath(component_folders): 653 return '%s%s' % (self.path_separator, 654 self.path_separator.join(component_folders)) 655 656 def _ValidRelativePath(file_path): 657 while file_path and '/..' in file_path: 658 file_path = file_path[:file_path.rfind('/..')] 659 if not self.Exists(self.NormalizePath(file_path)): 660 return False 661 return True 662 663 def _FollowLink(link_path_components, link): 664 """Follow a link w.r.t. a path resolved so far. 665 666 The component is either a real file, which is a no-op, or a symlink. 667 In the case of a symlink, we have to modify the path as built up so far 668 /a/b => ../c should yield /a/../c (which will normalize to /a/c) 669 /a/b => x should yield /a/x 670 /a/b => /x/y/z should yield /x/y/z 671 The modified path may land us in a new spot which is itself a 672 link, so we may repeat the process. 673 674 Args: 675 link_path_components: The resolved path built up to the link so far. 676 link: The link object itself. 677 678 Returns: 679 (string) the updated path resolved after following the link. 680 681 Raises: 682 IOError: if there are too many levels of symbolic link 683 """ 684 link_path = link.contents 685 # For links to absolute paths, we want to throw out everything in the 686 # path built so far and replace with the link. For relative links, we 687 # have to append the link to what we have so far, 688 if not link_path.startswith(self.path_separator): 689 # Relative path. Append remainder of path to what we have processed 690 # so far, excluding the name of the link itself. 691 # /a/b => ../c should yield /a/../c (which will normalize to /c) 692 # /a/b => d should yield a/d 693 components = link_path_components[:-1] 694 components.append(link_path) 695 link_path = self.path_separator.join(components) 696 # Don't call self.NormalizePath(), as we don't want to prepend self.cwd. 697 return self.CollapsePath(link_path) 698 699 if file_path is None: 700 # file.open(None) raises TypeError, so mimic that. 701 raise TypeError('Expected file system path string, received None') 702 if not file_path or not _ValidRelativePath(file_path): 703 # file.open('') raises IOError, so mimic that, and validate that all 704 # parts of a relative path exist. 705 raise IOError(errno.ENOENT, 706 'No such file or directory: \'%s\'' % file_path) 707 file_path = self.NormalizePath(file_path) 708 if file_path == self.root.name: 709 return file_path 710 711 current_dir = self.root 712 path_components = self.GetPathComponents(file_path) 713 714 resolved_components = [] 715 link_depth = 0 716 while path_components: 717 component = path_components.pop(0) 718 resolved_components.append(component) 719 if component not in current_dir.contents: 720 # The component of the path at this point does not actually exist in 721 # the folder. We can't resolve the path any more. It is legal to link 722 # to a file that does not yet exist, so rather than raise an error, we 723 # just append the remaining components to what return path we have built 724 # so far and return that. 725 resolved_components.extend(path_components) 726 break 727 current_dir = current_dir.contents[component] 728 729 # Resolve any possible symlinks in the current path component. 730 if stat.S_ISLNK(current_dir.st_mode): 731 # This link_depth check is not really meant to be an accurate check. 732 # It is just a quick hack to prevent us from looping forever on 733 # cycles. 734 link_depth += 1 735 if link_depth > _MAX_LINK_DEPTH: 736 raise IOError(errno.EMLINK, 737 'Too many levels of symbolic links: \'%s\'' % 738 _ComponentsToPath(resolved_components)) 739 link_path = _FollowLink(resolved_components, current_dir) 740 741 # Following the link might result in the complete replacement of the 742 # current_dir, so we evaluate the entire resulting path. 743 target_components = self.GetPathComponents(link_path) 744 path_components = target_components + path_components 745 resolved_components = [] 746 current_dir = self.root 747 return _ComponentsToPath(resolved_components) 748 749 def GetObjectFromNormalizedPath(self, file_path): 750 """Searches for the specified filesystem object within the fake filesystem. 751 752 Args: 753 file_path: specifies target FakeFile object to retrieve, with a 754 path that has already been normalized/resolved 755 756 Returns: 757 the FakeFile object corresponding to file_path 758 759 Raises: 760 IOError: if the object is not found 761 """ 762 if file_path == self.root.name: 763 return self.root 764 path_components = self.GetPathComponents(file_path) 765 target_object = self.root 766 try: 767 for component in path_components: 768 if not isinstance(target_object, FakeDirectory): 769 raise IOError(errno.ENOENT, 770 'No such file or directory in fake filesystem', 771 file_path) 772 target_object = target_object.GetEntry(component) 773 except KeyError: 774 raise IOError(errno.ENOENT, 775 'No such file or directory in fake filesystem', 776 file_path) 777 return target_object 778 779 def GetObject(self, file_path): 780 """Searches for the specified filesystem object within the fake filesystem. 781 782 Args: 783 file_path: specifies target FakeFile object to retrieve 784 785 Returns: 786 the FakeFile object corresponding to file_path 787 788 Raises: 789 IOError: if the object is not found 790 """ 791 file_path = self.NormalizePath(file_path) 792 return self.GetObjectFromNormalizedPath(file_path) 793 794 def ResolveObject(self, file_path): 795 """Searches for the specified filesystem object, resolving all links. 796 797 Args: 798 file_path: specifies target FakeFile object to retrieve 799 800 Returns: 801 the FakeFile object corresponding to file_path 802 803 Raises: 804 IOError: if the object is not found 805 """ 806 return self.GetObjectFromNormalizedPath(self.ResolvePath(file_path)) 807 808 def LResolveObject(self, path): 809 """Searches for the specified object, resolving only parent links. 810 811 This is analogous to the stat/lstat difference. This resolves links *to* 812 the object but not of the final object itself. 813 814 Args: 815 path: specifies target FakeFile object to retrieve 816 817 Returns: 818 the FakeFile object corresponding to path 819 820 Raises: 821 IOError: if the object is not found 822 """ 823 if path == self.root.name: 824 # The root directory will never be a link 825 return self.root 826 parent_directory, child_name = self.SplitPath(path) 827 if not parent_directory: 828 parent_directory = self.cwd 829 try: 830 parent_obj = self.ResolveObject(parent_directory) 831 assert parent_obj 832 if not isinstance(parent_obj, FakeDirectory): 833 raise IOError(errno.ENOENT, 834 'No such file or directory in fake filesystem', 835 path) 836 return parent_obj.GetEntry(child_name) 837 except KeyError: 838 raise IOError(errno.ENOENT, 839 'No such file or directory in the fake filesystem', 840 path) 841 842 def AddObject(self, file_path, file_object): 843 """Add a fake file or directory into the filesystem at file_path. 844 845 Args: 846 file_path: the path to the file to be added relative to self 847 file_object: file or directory to add 848 849 Raises: 850 IOError: if file_path does not correspond to a directory 851 """ 852 try: 853 target_directory = self.GetObject(file_path) 854 target_directory.AddEntry(file_object) 855 except AttributeError: 856 raise IOError(errno.ENOTDIR, 857 'Not a directory in the fake filesystem', 858 file_path) 859 860 def RemoveObject(self, file_path): 861 """Remove an existing file or directory. 862 863 Args: 864 file_path: the path to the file relative to self 865 866 Raises: 867 IOError: if file_path does not correspond to an existing file, or if part 868 of the path refers to something other than a directory 869 OSError: if the directory is in use (eg, if it is '/') 870 """ 871 if file_path == self.root.name: 872 raise OSError(errno.EBUSY, 'Fake device or resource busy', 873 file_path) 874 try: 875 dirname, basename = self.SplitPath(file_path) 876 target_directory = self.GetObject(dirname) 877 target_directory.RemoveEntry(basename) 878 except KeyError: 879 raise IOError(errno.ENOENT, 880 'No such file or directory in the fake filesystem', 881 file_path) 882 except AttributeError: 883 raise IOError(errno.ENOTDIR, 884 'Not a directory in the fake filesystem', 885 file_path) 886 887 def CreateDirectory(self, directory_path, perm_bits=PERM_DEF, inode=None): 888 """Creates directory_path, and all the parent directories. 889 890 Helper method to set up your test faster 891 892 Args: 893 directory_path: directory to create 894 perm_bits: permission bits 895 inode: inode of directory 896 897 Returns: 898 the newly created FakeDirectory object 899 900 Raises: 901 OSError: if the directory already exists 902 """ 903 directory_path = self.NormalizePath(directory_path) 904 if self.Exists(directory_path): 905 raise OSError(errno.EEXIST, 906 'Directory exists in fake filesystem', 907 directory_path) 908 path_components = self.GetPathComponents(directory_path) 909 current_dir = self.root 910 911 for component in path_components: 912 if component not in current_dir.contents: 913 new_dir = FakeDirectory(component, perm_bits) 914 current_dir.AddEntry(new_dir) 915 current_dir = new_dir 916 else: 917 current_dir = current_dir.contents[component] 918 919 current_dir.SetIno(inode) 920 return current_dir 921 922 def CreateFile(self, file_path, st_mode=stat.S_IFREG | PERM_DEF_FILE, 923 contents='', st_size=None, create_missing_dirs=True, 924 apply_umask=False, inode=None): 925 """Creates file_path, including all the parent directories along the way. 926 927 Helper method to set up your test faster. 928 929 Args: 930 file_path: path to the file to create 931 st_mode: the stat.S_IF constant representing the file type 932 contents: the contents of the file 933 st_size: file size; only valid if contents=None 934 create_missing_dirs: if True, auto create missing directories 935 apply_umask: whether or not the current umask must be applied on st_mode 936 inode: inode of the file 937 938 Returns: 939 the newly created FakeFile object 940 941 Raises: 942 IOError: if the file already exists 943 IOError: if the containing directory is required and missing 944 """ 945 file_path = self.NormalizePath(file_path) 946 if self.Exists(file_path): 947 raise IOError(errno.EEXIST, 948 'File already exists in fake filesystem', 949 file_path) 950 parent_directory, new_file = self.SplitPath(file_path) 951 if not parent_directory: 952 parent_directory = self.cwd 953 if not self.Exists(parent_directory): 954 if not create_missing_dirs: 955 raise IOError(errno.ENOENT, 'No such fake directory', parent_directory) 956 self.CreateDirectory(parent_directory) 957 if apply_umask: 958 st_mode &= ~self.umask 959 file_object = FakeFile(new_file, st_mode, contents) 960 file_object.SetIno(inode) 961 self.AddObject(parent_directory, file_object) 962 963 # set the size if st_size is given 964 if not contents and st_size is not None: 965 try: 966 file_object.SetLargeFileSize(st_size) 967 except IOError: 968 self.RemoveObject(file_path) 969 raise 970 971 return file_object 972 973 def CreateLink(self, file_path, link_target): 974 """Creates the specified symlink, pointed at the specified link target. 975 976 Args: 977 file_path: path to the symlink to create 978 link_target: the target of the symlink 979 980 Returns: 981 the newly created FakeFile object 982 983 Raises: 984 IOError: if the file already exists 985 """ 986 resolved_file_path = self.ResolvePath(file_path) 987 return self.CreateFile(resolved_file_path, st_mode=stat.S_IFLNK | PERM_DEF, 988 contents=link_target) 989 990 def __str__(self): 991 return str(self.root) 992 993 994class FakePathModule(object): 995 """Faked os.path module replacement. 996 997 FakePathModule should *only* be instantiated by FakeOsModule. See the 998 FakeOsModule docstring for details. 999 """ 1000 _OS_PATH_COPY = CopyModule(os.path) 1001 1002 def __init__(self, filesystem, os_module=None): 1003 """Init. 1004 1005 Args: 1006 filesystem: FakeFilesystem used to provide file system information 1007 os_module: (deprecated) FakeOsModule to assign to self.os 1008 """ 1009 self.filesystem = filesystem 1010 self._os_path = self._OS_PATH_COPY 1011 if os_module is None: 1012 warnings.warn(FAKE_PATH_MODULE_DEPRECATION, DeprecationWarning, 1013 stacklevel=2) 1014 self._os_path.os = self.os = os_module 1015 self.sep = self.filesystem.path_separator 1016 1017 def exists(self, path): 1018 """Determines whether the file object exists within the fake filesystem. 1019 1020 Args: 1021 path: path to the file object 1022 1023 Returns: 1024 bool (if file exists) 1025 """ 1026 return self.filesystem.Exists(path) 1027 1028 def lexists(self, path): 1029 """Test whether a path exists. Returns True for broken symbolic links. 1030 1031 Args: 1032 path: path to the symlnk object 1033 1034 Returns: 1035 bool (if file exists) 1036 """ 1037 return self.exists(path) or self.islink(path) 1038 1039 def getsize(self, path): 1040 """Return the file object size in bytes. 1041 1042 Args: 1043 path: path to the file object 1044 1045 Returns: 1046 file size in bytes 1047 """ 1048 file_obj = self.filesystem.GetObject(path) 1049 return file_obj.st_size 1050 1051 def _istype(self, path, st_flag): 1052 """Helper function to implement isdir(), islink(), etc. 1053 1054 See the stat(2) man page for valid stat.S_I* flag values 1055 1056 Args: 1057 path: path to file to stat and test 1058 st_flag: the stat.S_I* flag checked for the file's st_mode 1059 1060 Returns: 1061 boolean (the st_flag is set in path's st_mode) 1062 1063 Raises: 1064 TypeError: if path is None 1065 """ 1066 if path is None: 1067 raise TypeError 1068 try: 1069 obj = self.filesystem.ResolveObject(path) 1070 if obj: 1071 return stat.S_IFMT(obj.st_mode) == st_flag 1072 except IOError: 1073 return False 1074 return False 1075 1076 def isabs(self, path): 1077 if self.filesystem.path_separator == os.path.sep: 1078 # Pass through to os.path.isabs, which on Windows has special 1079 # handling for a leading drive letter. 1080 return self._os_path.isabs(path) 1081 else: 1082 return path.startswith(self.filesystem.path_separator) 1083 1084 def isdir(self, path): 1085 """Determines if path identifies a directory.""" 1086 return self._istype(path, stat.S_IFDIR) 1087 1088 def isfile(self, path): 1089 """Determines if path identifies a regular file.""" 1090 return self._istype(path, stat.S_IFREG) 1091 1092 def islink(self, path): 1093 """Determines if path identifies a symbolic link. 1094 1095 Args: 1096 path: path to filesystem object. 1097 1098 Returns: 1099 boolean (the st_flag is set in path's st_mode) 1100 1101 Raises: 1102 TypeError: if path is None 1103 """ 1104 if path is None: 1105 raise TypeError 1106 try: 1107 link_obj = self.filesystem.LResolveObject(path) 1108 return stat.S_IFMT(link_obj.st_mode) == stat.S_IFLNK 1109 except IOError: 1110 return False 1111 except KeyError: 1112 return False 1113 return False 1114 1115 def getmtime(self, path): 1116 """Returns the mtime of the file.""" 1117 try: 1118 file_obj = self.filesystem.GetObject(path) 1119 except IOError as e: 1120 raise OSError(errno.ENOENT, str(e)) 1121 return file_obj.st_mtime 1122 1123 def abspath(self, path): 1124 """Return the absolute version of a path.""" 1125 if not self.isabs(path): 1126 if sys.version_info < (3, 0) and isinstance(path, unicode): 1127 cwd = self.os.getcwdu() 1128 else: 1129 cwd = self.os.getcwd() 1130 path = self.join(cwd, path) 1131 return self.normpath(path) 1132 1133 def join(self, *p): 1134 """Returns the completed path with a separator of the parts.""" 1135 return self.filesystem.JoinPaths(*p) 1136 1137 def normpath(self, path): 1138 """Normalize path, eliminating double slashes, etc.""" 1139 return self.filesystem.CollapsePath(path) 1140 1141 if _is_windows: 1142 1143 def relpath(self, path, start=None): 1144 """ntpath.relpath() needs the cwd passed in the start argument.""" 1145 if start is None: 1146 start = self.filesystem.cwd 1147 path = self._os_path.relpath(path, start) 1148 return path.replace(self._os_path.sep, self.filesystem.path_separator) 1149 1150 realpath = abspath 1151 1152 def __getattr__(self, name): 1153 """Forwards any non-faked calls to os.path.""" 1154 return self._os_path.__dict__[name] 1155 1156 1157class FakeOsModule(object): 1158 """Uses FakeFilesystem to provide a fake os module replacement. 1159 1160 Do not create os.path separately from os, as there is a necessary circular 1161 dependency between os and os.path to replicate the behavior of the standard 1162 Python modules. What you want to do is to just let FakeOsModule take care of 1163 os.path setup itself. 1164 1165 # You always want to do this. 1166 filesystem = fake_filesystem.FakeFilesystem() 1167 my_os_module = fake_filesystem.FakeOsModule(filesystem) 1168 """ 1169 1170 def __init__(self, filesystem, os_path_module=None): 1171 """Also exposes self.path (to fake os.path). 1172 1173 Args: 1174 filesystem: FakeFilesystem used to provide file system information 1175 os_path_module: (deprecated) optional FakePathModule instance 1176 """ 1177 self.filesystem = filesystem 1178 self.sep = filesystem.path_separator 1179 self._os_module = os 1180 if os_path_module is None: 1181 self.path = FakePathModule(self.filesystem, self) 1182 else: 1183 warnings.warn(FAKE_PATH_MODULE_DEPRECATION, DeprecationWarning, 1184 stacklevel=2) 1185 self.path = os_path_module 1186 if sys.version_info < (3, 0): 1187 self.fdopen = self._fdopen_ver2 1188 else: 1189 self.fdopen = self._fdopen 1190 1191 def _fdopen(self, *args, **kwargs): 1192 """Redirector to open() builtin function. 1193 1194 Args: 1195 *args: pass through args 1196 **kwargs: pass through kwargs 1197 1198 Returns: 1199 File object corresponding to file_des. 1200 1201 Raises: 1202 TypeError: if file descriptor is not an integer. 1203 """ 1204 if not isinstance(args[0], int): 1205 raise TypeError('an integer is required') 1206 return FakeFileOpen(self.filesystem)(*args, **kwargs) 1207 1208 def _fdopen_ver2(self, file_des, mode='r', bufsize=None): 1209 """Returns an open file object connected to the file descriptor file_des. 1210 1211 Args: 1212 file_des: An integer file descriptor for the file object requested. 1213 mode: additional file flags. Currently checks to see if the mode matches 1214 the mode of the requested file object. 1215 bufsize: ignored. (Used for signature compliance with __builtin__.fdopen) 1216 1217 Returns: 1218 File object corresponding to file_des. 1219 1220 Raises: 1221 OSError: if bad file descriptor or incompatible mode is given. 1222 TypeError: if file descriptor is not an integer. 1223 """ 1224 if not isinstance(file_des, int): 1225 raise TypeError('an integer is required') 1226 1227 try: 1228 return FakeFileOpen(self.filesystem).Call(file_des, mode=mode) 1229 except IOError as e: 1230 raise OSError(e) 1231 1232 def open(self, file_path, flags, mode=None): 1233 """Returns the file descriptor for a FakeFile. 1234 1235 WARNING: This implementation only implements creating a file. Please fill 1236 out the remainder for your needs. 1237 1238 Args: 1239 file_path: the path to the file 1240 flags: low-level bits to indicate io operation 1241 mode: bits to define default permissions 1242 1243 Returns: 1244 A file descriptor. 1245 1246 Raises: 1247 OSError: if the path cannot be found 1248 ValueError: if invalid mode is given 1249 NotImplementedError: if an unsupported flag is passed in 1250 """ 1251 if flags & os.O_CREAT: 1252 fake_file = FakeFileOpen(self.filesystem)(file_path, 'w') 1253 if mode: 1254 self.chmod(file_path, mode) 1255 return fake_file.fileno() 1256 else: 1257 raise NotImplementedError('FakeOsModule.open') 1258 1259 def close(self, file_des): 1260 """Closes a file descriptor. 1261 1262 Args: 1263 file_des: An integer file descriptor for the file object requested. 1264 1265 Raises: 1266 OSError: bad file descriptor. 1267 TypeError: if file descriptor is not an integer. 1268 """ 1269 fh = self.filesystem.GetOpenFile(file_des) 1270 fh.close() 1271 1272 def read(self, file_des, num_bytes): 1273 """Reads number of bytes from a file descriptor, returns bytes read. 1274 1275 Args: 1276 file_des: An integer file descriptor for the file object requested. 1277 num_bytes: Number of bytes to read from file. 1278 1279 Returns: 1280 Bytes read from file. 1281 1282 Raises: 1283 OSError: bad file descriptor. 1284 TypeError: if file descriptor is not an integer. 1285 """ 1286 fh = self.filesystem.GetOpenFile(file_des) 1287 return fh.read(num_bytes) 1288 1289 def write(self, file_des, contents): 1290 """Writes string to file descriptor, returns number of bytes written. 1291 1292 Args: 1293 file_des: An integer file descriptor for the file object requested. 1294 contents: String of bytes to write to file. 1295 1296 Returns: 1297 Number of bytes written. 1298 1299 Raises: 1300 OSError: bad file descriptor. 1301 TypeError: if file descriptor is not an integer. 1302 """ 1303 fh = self.filesystem.GetOpenFile(file_des) 1304 fh.write(contents) 1305 fh.flush() 1306 return len(contents) 1307 1308 def fstat(self, file_des): 1309 """Returns the os.stat-like tuple for the FakeFile object of file_des. 1310 1311 Args: 1312 file_des: file descriptor of filesystem object to retrieve 1313 1314 Returns: 1315 the os.stat_result object corresponding to entry_path 1316 1317 Raises: 1318 OSError: if the filesystem object doesn't exist. 1319 """ 1320 # stat should return the tuple representing return value of os.stat 1321 stats = self.filesystem.GetOpenFile(file_des).GetObject() 1322 st_obj = os.stat_result((stats.st_mode, stats.st_ino, stats.st_dev, 1323 stats.st_nlink, stats.st_uid, stats.st_gid, 1324 stats.st_size, stats.st_atime, 1325 stats.st_mtime, stats.st_ctime)) 1326 return st_obj 1327 1328 def _ConfirmDir(self, target_directory): 1329 """Tests that the target is actually a directory, raising OSError if not. 1330 1331 Args: 1332 target_directory: path to the target directory within the fake 1333 filesystem 1334 1335 Returns: 1336 the FakeFile object corresponding to target_directory 1337 1338 Raises: 1339 OSError: if the target is not a directory 1340 """ 1341 try: 1342 directory = self.filesystem.GetObject(target_directory) 1343 except IOError as e: 1344 raise OSError(e.errno, e.strerror, target_directory) 1345 if not directory.st_mode & stat.S_IFDIR: 1346 raise OSError(errno.ENOTDIR, 1347 'Fake os module: not a directory', 1348 target_directory) 1349 return directory 1350 1351 def umask(self, new_mask): 1352 """Change the current umask. 1353 1354 Args: 1355 new_mask: An integer. 1356 1357 Returns: 1358 The old mask. 1359 1360 Raises: 1361 TypeError: new_mask is of an invalid type. 1362 """ 1363 if not isinstance(new_mask, int): 1364 raise TypeError('an integer is required') 1365 old_umask = self.filesystem.umask 1366 self.filesystem.umask = new_mask 1367 return old_umask 1368 1369 def chdir(self, target_directory): 1370 """Change current working directory to target directory. 1371 1372 Args: 1373 target_directory: path to new current working directory 1374 1375 Raises: 1376 OSError: if user lacks permission to enter the argument directory or if 1377 the target is not a directory 1378 """ 1379 target_directory = self.filesystem.ResolvePath(target_directory) 1380 self._ConfirmDir(target_directory) 1381 directory = self.filesystem.GetObject(target_directory) 1382 # A full implementation would check permissions all the way up the tree. 1383 if not directory.st_mode | PERM_EXE: 1384 raise OSError(errno.EACCES, 'Fake os module: permission denied', 1385 directory) 1386 self.filesystem.cwd = target_directory 1387 1388 def getcwd(self): 1389 """Return current working directory.""" 1390 return self.filesystem.cwd 1391 1392 def getcwdu(self): 1393 """Return current working directory. Deprecated in Python 3.""" 1394 if sys.version_info >= (3, 0): 1395 raise AttributeError('no attribute getcwdu') 1396 return unicode(self.filesystem.cwd) 1397 1398 def listdir(self, target_directory): 1399 """Returns a sorted list of filenames in target_directory. 1400 1401 Args: 1402 target_directory: path to the target directory within the fake 1403 filesystem 1404 1405 Returns: 1406 a sorted list of file names within the target directory 1407 1408 Raises: 1409 OSError: if the target is not a directory 1410 """ 1411 target_directory = self.filesystem.ResolvePath(target_directory) 1412 directory = self._ConfirmDir(target_directory) 1413 return sorted(directory.contents) 1414 1415 def _ClassifyDirectoryContents(self, root): 1416 """Classify contents of a directory as files/directories. 1417 1418 Args: 1419 root: (str) Directory to examine. 1420 1421 Returns: 1422 (tuple) A tuple consisting of three values: the directory examined, a 1423 list containing all of the directory entries, and a list containing all 1424 of the non-directory entries. (This is the same format as returned by 1425 the os.walk generator.) 1426 1427 Raises: 1428 Nothing on its own, but be ready to catch exceptions generated by 1429 underlying mechanisms like os.listdir. 1430 """ 1431 dirs = [] 1432 files = [] 1433 for entry in self.listdir(root): 1434 if self.path.isdir(self.path.join(root, entry)): 1435 dirs.append(entry) 1436 else: 1437 files.append(entry) 1438 return (root, dirs, files) 1439 1440 def walk(self, top, topdown=True, onerror=None): 1441 """Performs an os.walk operation over the fake filesystem. 1442 1443 Args: 1444 top: root directory from which to begin walk 1445 topdown: determines whether to return the tuples with the root as the 1446 first entry (True) or as the last, after all the child directory 1447 tuples (False) 1448 onerror: if not None, function which will be called to handle the 1449 os.error instance provided when os.listdir() fails 1450 1451 Yields: 1452 (path, directories, nondirectories) for top and each of its 1453 subdirectories. See the documentation for the builtin os module for 1454 further details. 1455 """ 1456 top = self.path.normpath(top) 1457 try: 1458 top_contents = self._ClassifyDirectoryContents(top) 1459 except OSError as e: 1460 top_contents = None 1461 if onerror is not None: 1462 onerror(e) 1463 1464 if top_contents is not None: 1465 if topdown: 1466 yield top_contents 1467 1468 for directory in top_contents[1]: 1469 for contents in self.walk(self.path.join(top, directory), 1470 topdown=topdown, onerror=onerror): 1471 yield contents 1472 1473 if not topdown: 1474 yield top_contents 1475 1476 def readlink(self, path): 1477 """Reads the target of a symlink. 1478 1479 Args: 1480 path: symlink to read the target of 1481 1482 Returns: 1483 the string representing the path to which the symbolic link points. 1484 1485 Raises: 1486 TypeError: if path is None 1487 OSError: (with errno=ENOENT) if path is not a valid path, or 1488 (with errno=EINVAL) if path is valid, but is not a symlink 1489 """ 1490 if path is None: 1491 raise TypeError 1492 try: 1493 link_obj = self.filesystem.LResolveObject(path) 1494 except IOError: 1495 raise OSError(errno.ENOENT, 'Fake os module: path does not exist', path) 1496 if stat.S_IFMT(link_obj.st_mode) != stat.S_IFLNK: 1497 raise OSError(errno.EINVAL, 'Fake os module: not a symlink', path) 1498 return link_obj.contents 1499 1500 def stat(self, entry_path): 1501 """Returns the os.stat-like tuple for the FakeFile object of entry_path. 1502 1503 Args: 1504 entry_path: path to filesystem object to retrieve 1505 1506 Returns: 1507 the os.stat_result object corresponding to entry_path 1508 1509 Raises: 1510 OSError: if the filesystem object doesn't exist. 1511 """ 1512 # stat should return the tuple representing return value of os.stat 1513 try: 1514 stats = self.filesystem.ResolveObject(entry_path) 1515 st_obj = os.stat_result((stats.st_mode, stats.st_ino, stats.st_dev, 1516 stats.st_nlink, stats.st_uid, stats.st_gid, 1517 stats.st_size, stats.st_atime, 1518 stats.st_mtime, stats.st_ctime)) 1519 return st_obj 1520 except IOError as io_error: 1521 raise OSError(io_error.errno, io_error.strerror, entry_path) 1522 1523 def lstat(self, entry_path): 1524 """Returns the os.stat-like tuple for entry_path, not following symlinks. 1525 1526 Args: 1527 entry_path: path to filesystem object to retrieve 1528 1529 Returns: 1530 the os.stat_result object corresponding to entry_path 1531 1532 Raises: 1533 OSError: if the filesystem object doesn't exist. 1534 """ 1535 # stat should return the tuple representing return value of os.stat 1536 try: 1537 stats = self.filesystem.LResolveObject(entry_path) 1538 st_obj = os.stat_result((stats.st_mode, stats.st_ino, stats.st_dev, 1539 stats.st_nlink, stats.st_uid, stats.st_gid, 1540 stats.st_size, stats.st_atime, 1541 stats.st_mtime, stats.st_ctime)) 1542 return st_obj 1543 except IOError as io_error: 1544 raise OSError(io_error.errno, io_error.strerror, entry_path) 1545 1546 def remove(self, path): 1547 """Removes the FakeFile object representing the specified file.""" 1548 path = self.filesystem.NormalizePath(path) 1549 if self.path.isdir(path) and not self.path.islink(path): 1550 raise OSError(errno.EISDIR, "Is a directory: '%s'" % path) 1551 try: 1552 self.filesystem.RemoveObject(path) 1553 except IOError as e: 1554 raise OSError(e.errno, e.strerror, e.filename) 1555 1556 # As per the documentation unlink = remove. 1557 unlink = remove 1558 1559 def rename(self, old_file, new_file): 1560 """Adds a FakeFile object at new_file containing contents of old_file. 1561 1562 Also removes the FakeFile object for old_file, and replaces existing 1563 new_file object, if one existed. 1564 1565 Args: 1566 old_file: path to filesystem object to rename 1567 new_file: path to where the filesystem object will live after this call 1568 1569 Raises: 1570 OSError: if old_file does not exist. 1571 IOError: if dirname(new_file) does not exist 1572 """ 1573 old_file = self.filesystem.NormalizePath(old_file) 1574 new_file = self.filesystem.NormalizePath(new_file) 1575 if not self.filesystem.Exists(old_file): 1576 raise OSError(errno.ENOENT, 1577 'Fake os object: can not rename nonexistent file ' 1578 'with name', 1579 old_file) 1580 if self.filesystem.Exists(new_file): 1581 if old_file == new_file: 1582 return None # Nothing to do here. 1583 else: 1584 self.remove(new_file) 1585 old_dir, old_name = self.path.split(old_file) 1586 new_dir, new_name = self.path.split(new_file) 1587 if not self.filesystem.Exists(new_dir): 1588 raise IOError(errno.ENOENT, 'No such fake directory', new_dir) 1589 old_dir_object = self.filesystem.ResolveObject(old_dir) 1590 old_object = old_dir_object.GetEntry(old_name) 1591 old_object_mtime = old_object.st_mtime 1592 new_dir_object = self.filesystem.ResolveObject(new_dir) 1593 if old_object.st_mode & stat.S_IFDIR: 1594 old_object.name = new_name 1595 new_dir_object.AddEntry(old_object) 1596 old_dir_object.RemoveEntry(old_name) 1597 else: 1598 self.filesystem.CreateFile(new_file, 1599 st_mode=old_object.st_mode, 1600 contents=old_object.contents, 1601 create_missing_dirs=False) 1602 self.remove(old_file) 1603 new_object = self.filesystem.GetObject(new_file) 1604 new_object.SetMTime(old_object_mtime) 1605 self.chown(new_file, old_object.st_uid, old_object.st_gid) 1606 1607 def rmdir(self, target_directory): 1608 """Remove a leaf Fake directory. 1609 1610 Args: 1611 target_directory: (str) Name of directory to remove. 1612 1613 Raises: 1614 OSError: if target_directory does not exist or is not a directory, 1615 or as per FakeFilesystem.RemoveObject. Cannot remove '.'. 1616 """ 1617 if target_directory == '.': 1618 raise OSError(errno.EINVAL, 'Invalid argument: \'.\'') 1619 target_directory = self.filesystem.NormalizePath(target_directory) 1620 if self._ConfirmDir(target_directory): 1621 if self.listdir(target_directory): 1622 raise OSError(errno.ENOTEMPTY, 'Fake Directory not empty', 1623 target_directory) 1624 try: 1625 self.filesystem.RemoveObject(target_directory) 1626 except IOError as e: 1627 raise OSError(e.errno, e.strerror, e.filename) 1628 1629 def removedirs(self, target_directory): 1630 """Remove a leaf Fake directory and all empty intermediate ones.""" 1631 target_directory = self.filesystem.NormalizePath(target_directory) 1632 directory = self._ConfirmDir(target_directory) 1633 if directory.contents: 1634 raise OSError(errno.ENOTEMPTY, 'Fake Directory not empty', 1635 self.path.basename(target_directory)) 1636 else: 1637 self.rmdir(target_directory) 1638 head, tail = self.path.split(target_directory) 1639 if not tail: 1640 head, tail = self.path.split(head) 1641 while head and tail: 1642 head_dir = self._ConfirmDir(head) 1643 if head_dir.contents: 1644 break 1645 self.rmdir(head) 1646 head, tail = self.path.split(head) 1647 1648 def mkdir(self, dir_name, mode=PERM_DEF): 1649 """Create a leaf Fake directory. 1650 1651 Args: 1652 dir_name: (str) Name of directory to create. Relative paths are assumed 1653 to be relative to '/'. 1654 mode: (int) Mode to create directory with. This argument defaults to 1655 0o777. The umask is applied to this mode. 1656 1657 Raises: 1658 OSError: if the directory name is invalid or parent directory is read only 1659 or as per FakeFilesystem.AddObject. 1660 """ 1661 if dir_name.endswith(self.sep): 1662 dir_name = dir_name[:-1] 1663 1664 parent_dir, _ = self.path.split(dir_name) 1665 if parent_dir: 1666 base_dir = self.path.normpath(parent_dir) 1667 if parent_dir.endswith(self.sep + '..'): 1668 base_dir, unused_dotdot, _ = parent_dir.partition(self.sep + '..') 1669 if not self.filesystem.Exists(base_dir): 1670 raise OSError(errno.ENOENT, 'No such fake directory', base_dir) 1671 1672 dir_name = self.filesystem.NormalizePath(dir_name) 1673 if self.filesystem.Exists(dir_name): 1674 raise OSError(errno.EEXIST, 'Fake object already exists', dir_name) 1675 head, tail = self.path.split(dir_name) 1676 directory_object = self.filesystem.GetObject(head) 1677 if not directory_object.st_mode & PERM_WRITE: 1678 raise OSError(errno.EACCES, 'Permission Denied', dir_name) 1679 1680 self.filesystem.AddObject( 1681 head, FakeDirectory(tail, mode & ~self.filesystem.umask)) 1682 1683 def makedirs(self, dir_name, mode=PERM_DEF): 1684 """Create a leaf Fake directory + create any non-existent parent dirs. 1685 1686 Args: 1687 dir_name: (str) Name of directory to create. 1688 mode: (int) Mode to create directory (and any necessary parent 1689 directories) with. This argument defaults to 0o777. The umask is 1690 applied to this mode. 1691 1692 Raises: 1693 OSError: if the directory already exists or as per 1694 FakeFilesystem.CreateDirectory 1695 """ 1696 dir_name = self.filesystem.NormalizePath(dir_name) 1697 path_components = self.filesystem.GetPathComponents(dir_name) 1698 1699 # Raise a permission denied error if the first existing directory is not 1700 # writeable. 1701 current_dir = self.filesystem.root 1702 for component in path_components: 1703 if component not in current_dir.contents: 1704 if not current_dir.st_mode & PERM_WRITE: 1705 raise OSError(errno.EACCES, 'Permission Denied', dir_name) 1706 else: 1707 break 1708 else: 1709 current_dir = current_dir.contents[component] 1710 1711 self.filesystem.CreateDirectory(dir_name, mode & ~self.filesystem.umask) 1712 1713 def access(self, path, mode): 1714 """Check if a file exists and has the specified permissions. 1715 1716 Args: 1717 path: (str) Path to the file. 1718 mode: (int) Permissions represented as a bitwise-OR combination of 1719 os.F_OK, os.R_OK, os.W_OK, and os.X_OK. 1720 Returns: 1721 boolean, True if file is accessible, False otherwise 1722 """ 1723 try: 1724 st = self.stat(path) 1725 except OSError as os_error: 1726 if os_error.errno == errno.ENOENT: 1727 return False 1728 raise 1729 return (mode & ((st.st_mode >> 6) & 7)) == mode 1730 1731 def chmod(self, path, mode): 1732 """Change the permissions of a file as encoded in integer mode. 1733 1734 Args: 1735 path: (str) Path to the file. 1736 mode: (int) Permissions 1737 """ 1738 try: 1739 file_object = self.filesystem.GetObject(path) 1740 except IOError as io_error: 1741 if io_error.errno == errno.ENOENT: 1742 raise OSError(errno.ENOENT, 1743 'No such file or directory in fake filesystem', 1744 path) 1745 raise 1746 file_object.st_mode = ((file_object.st_mode & ~PERM_ALL) | 1747 (mode & PERM_ALL)) 1748 file_object.st_ctime = int(time.time()) 1749 1750 def utime(self, path, times): 1751 """Change the access and modified times of a file. 1752 1753 Args: 1754 path: (str) Path to the file. 1755 times: 2-tuple of numbers, of the form (atime, mtime) which is used to set 1756 the access and modified times, respectively. If None, file's access 1757 and modified times are set to the current time. 1758 1759 Raises: 1760 TypeError: If anything other than integers is specified in passed tuple or 1761 number of elements in the tuple is not equal to 2. 1762 """ 1763 try: 1764 file_object = self.filesystem.ResolveObject(path) 1765 except IOError as io_error: 1766 if io_error.errno == errno.ENOENT: 1767 raise OSError(errno.ENOENT, 1768 'No such file or directory in fake filesystem', 1769 path) 1770 raise 1771 if times is None: 1772 file_object.st_atime = int(time.time()) 1773 file_object.st_mtime = int(time.time()) 1774 else: 1775 if len(times) != 2: 1776 raise TypeError('utime() arg 2 must be a tuple (atime, mtime)') 1777 for t in times: 1778 if not isinstance(t, (int, float)): 1779 raise TypeError('atime and mtime must be numbers') 1780 1781 file_object.st_atime = times[0] 1782 file_object.st_mtime = times[1] 1783 1784 def chown(self, path, uid, gid): 1785 """Set ownership of a faked file. 1786 1787 Args: 1788 path: (str) Path to the file or directory. 1789 uid: (int) Numeric uid to set the file or directory to. 1790 gid: (int) Numeric gid to set the file or directory to. 1791 1792 `None` is also allowed for `uid` and `gid`. This permits `os.rename` to 1793 use `os.chown` even when the source file `uid` and `gid` are `None` (unset). 1794 """ 1795 try: 1796 file_object = self.filesystem.GetObject(path) 1797 except IOError as io_error: 1798 if io_error.errno == errno.ENOENT: 1799 raise OSError(errno.ENOENT, 1800 'No such file or directory in fake filesystem', 1801 path) 1802 if not ((isinstance(uid, int) or uid is None) and 1803 (isinstance(gid, int) or gid is None)): 1804 raise TypeError("An integer is required") 1805 if uid != -1: 1806 file_object.st_uid = uid 1807 if gid != -1: 1808 file_object.st_gid = gid 1809 1810 def mknod(self, filename, mode=None, device=None): 1811 """Create a filesystem node named 'filename'. 1812 1813 Does not support device special files or named pipes as the real os 1814 module does. 1815 1816 Args: 1817 filename: (str) Name of the file to create 1818 mode: (int) permissions to use and type of file to be created. 1819 Default permissions are 0o666. Only the stat.S_IFREG file type 1820 is supported by the fake implementation. The umask is applied 1821 to this mode. 1822 device: not supported in fake implementation 1823 1824 Raises: 1825 OSError: if called with unsupported options or the file can not be 1826 created. 1827 """ 1828 if mode is None: 1829 mode = stat.S_IFREG | PERM_DEF_FILE 1830 if device or not mode & stat.S_IFREG: 1831 raise OSError(errno.EINVAL, 1832 'Fake os mknod implementation only supports ' 1833 'regular files.') 1834 1835 head, tail = self.path.split(filename) 1836 if not tail: 1837 if self.filesystem.Exists(head): 1838 raise OSError(errno.EEXIST, 'Fake filesystem: %s: %s' % ( 1839 os.strerror(errno.EEXIST), filename)) 1840 raise OSError(errno.ENOENT, 'Fake filesystem: %s: %s' % ( 1841 os.strerror(errno.ENOENT), filename)) 1842 if tail == '.' or tail == '..' or self.filesystem.Exists(filename): 1843 raise OSError(errno.EEXIST, 'Fake fileystem: %s: %s' % ( 1844 os.strerror(errno.EEXIST), filename)) 1845 try: 1846 self.filesystem.AddObject(head, FakeFile(tail, 1847 mode & ~self.filesystem.umask)) 1848 except IOError: 1849 raise OSError(errno.ENOTDIR, 'Fake filesystem: %s: %s' % ( 1850 os.strerror(errno.ENOTDIR), filename)) 1851 1852 def symlink(self, link_target, path): 1853 """Creates the specified symlink, pointed at the specified link target. 1854 1855 Args: 1856 link_target: the target of the symlink 1857 path: path to the symlink to create 1858 1859 Returns: 1860 None 1861 1862 Raises: 1863 IOError: if the file already exists 1864 """ 1865 self.filesystem.CreateLink(path, link_target) 1866 1867 # pylint: disable-msg=C6002 1868 # TODO: Link doesn't behave like os.link, this needs to be fixed properly. 1869 link = symlink 1870 1871 def __getattr__(self, name): 1872 """Forwards any unfaked calls to the standard os module.""" 1873 return getattr(self._os_module, name) 1874 1875 1876class FakeFileOpen(object): 1877 """Faked file() and open() function replacements. 1878 1879 Returns FakeFile objects in a FakeFilesystem in place of the file() 1880 or open() function. 1881 """ 1882 1883 def __init__(self, filesystem, delete_on_close=False): 1884 """init. 1885 1886 Args: 1887 filesystem: FakeFilesystem used to provide file system information 1888 delete_on_close: optional boolean, deletes file on close() 1889 """ 1890 self.filesystem = filesystem 1891 self._delete_on_close = delete_on_close 1892 1893 def __call__(self, *args, **kwargs): 1894 """Redirects calls to file() or open() to appropriate method.""" 1895 if sys.version_info < (3, 0): 1896 return self._call_ver2(*args, **kwargs) 1897 else: 1898 return self.Call(*args, **kwargs) 1899 1900 def _call_ver2(self, file_path, mode='r', buffering=-1, flags=None): 1901 """Limits args of open() or file() for Python 2.x versions.""" 1902 # Backwards compatibility, mode arg used to be named flags 1903 mode = flags or mode 1904 return self.Call(file_path, mode, buffering) 1905 1906 def Call(self, file_, mode='r', buffering=-1, encoding=None, 1907 errors=None, newline=None, closefd=True, opener=None): 1908 """Returns a StringIO object with the contents of the target file object. 1909 1910 Args: 1911 file_: path to target file or a file descriptor 1912 mode: additional file modes. All r/w/a r+/w+/a+ modes are supported. 1913 't', and 'U' are ignored, e.g., 'wU' is treated as 'w'. 'b' sets 1914 binary mode, no end of line translations in StringIO. 1915 buffering: ignored. (Used for signature compliance with __builtin__.open) 1916 encoding: ignored, strings have no encoding 1917 errors: ignored, this relates to encoding 1918 newline: controls universal newlines, passed to StringIO object 1919 closefd: if a file descriptor rather than file name is passed, and set 1920 to false, then the file descriptor is kept open when file is closed 1921 opener: not supported 1922 1923 Returns: 1924 a StringIO object containing the contents of the target file 1925 1926 Raises: 1927 IOError: if the target object is a directory, the path is invalid or 1928 permission is denied. 1929 """ 1930 orig_modes = mode # Save original mdoes for error messages. 1931 # Binary mode for non 3.x or set by mode 1932 binary = sys.version_info < (3, 0) or 'b' in mode 1933 # Normalize modes. Ignore 't' and 'U'. 1934 mode = mode.replace('t', '').replace('b', '') 1935 mode = mode.replace('rU', 'r').replace('U', 'r') 1936 1937 if mode not in _OPEN_MODE_MAP: 1938 raise IOError('Invalid mode: %r' % orig_modes) 1939 1940 must_exist, need_read, need_write, truncate, append = _OPEN_MODE_MAP[mode] 1941 1942 file_object = None 1943 filedes = None 1944 # opening a file descriptor 1945 if isinstance(file_, int): 1946 filedes = file_ 1947 file_object = self.filesystem.GetOpenFile(filedes).GetObject() 1948 file_path = file_object.name 1949 else: 1950 file_path = file_ 1951 real_path = self.filesystem.ResolvePath(file_path) 1952 if self.filesystem.Exists(file_path): 1953 file_object = self.filesystem.GetObjectFromNormalizedPath(real_path) 1954 closefd = True 1955 1956 if file_object: 1957 if ((need_read and not file_object.st_mode & PERM_READ) or 1958 (need_write and not file_object.st_mode & PERM_WRITE)): 1959 raise IOError(errno.EACCES, 'Permission denied', file_path) 1960 if need_write: 1961 file_object.st_ctime = int(time.time()) 1962 if truncate: 1963 file_object.SetContents('') 1964 else: 1965 if must_exist: 1966 raise IOError(errno.ENOENT, 'No such file or directory', file_path) 1967 file_object = self.filesystem.CreateFile( 1968 real_path, create_missing_dirs=False, apply_umask=True) 1969 1970 if file_object.st_mode & stat.S_IFDIR: 1971 raise IOError(errno.EISDIR, 'Fake file object: is a directory', file_path) 1972 1973 class FakeFileWrapper(object): 1974 """Wrapper for a StringIO object for use by a FakeFile object. 1975 1976 If the wrapper has any data written to it, it will propagate to 1977 the FakeFile object on close() or flush(). 1978 """ 1979 if sys.version_info < (3, 0): 1980 _OPERATION_ERROR = IOError 1981 else: 1982 _OPERATION_ERROR = io.UnsupportedOperation 1983 1984 def __init__(self, file_object, update=False, read=False, append=False, 1985 delete_on_close=False, filesystem=None, newline=None, 1986 binary=True, closefd=True): 1987 self._file_object = file_object 1988 self._append = append 1989 self._read = read 1990 self._update = update 1991 self._closefd = closefd 1992 self._file_epoch = file_object.epoch 1993 contents = file_object.contents 1994 newline_arg = {} if binary else {'newline': newline} 1995 io_class = io.StringIO 1996 if contents and isinstance(contents, Hexlified): 1997 contents = contents.recover(binary) 1998 # For Python 3, files opened as binary only read/write byte contents. 1999 if sys.version_info >= (3, 0) and binary: 2000 io_class = io.BytesIO 2001 if contents and isinstance(contents, str): 2002 contents = bytes(contents, 'ascii') 2003 if contents: 2004 if update: 2005 self._io = io_class(**newline_arg) 2006 self._io.write(contents) 2007 if not append: 2008 self._io.seek(0) 2009 else: 2010 self._read_whence = 0 2011 if read: 2012 self._read_seek = 0 2013 else: 2014 self._read_seek = self._io.tell() 2015 else: 2016 self._io = io_class(contents, **newline_arg) 2017 else: 2018 self._io = io_class(**newline_arg) 2019 self._read_whence = 0 2020 self._read_seek = 0 2021 if delete_on_close: 2022 assert filesystem, 'delete_on_close=True requires filesystem=' 2023 self._filesystem = filesystem 2024 self._delete_on_close = delete_on_close 2025 # override, don't modify FakeFile.name, as FakeFilesystem expects 2026 # it to be the file name only, no directories. 2027 self.name = file_object.opened_as 2028 2029 def __enter__(self): 2030 """To support usage of this fake file with the 'with' statement.""" 2031 return self 2032 2033 def __exit__(self, type, value, traceback): # pylint: disable-msg=W0622 2034 """To support usage of this fake file with the 'with' statement.""" 2035 self.close() 2036 2037 def GetObject(self): 2038 """Returns FakeFile object that is wrapped by current class.""" 2039 return self._file_object 2040 2041 def fileno(self): 2042 """Returns file descriptor of file object.""" 2043 return self.filedes 2044 2045 def close(self): 2046 """File close.""" 2047 if self._update: 2048 self._file_object.SetContents(self._io.getvalue()) 2049 if self._closefd: 2050 self._filesystem.CloseOpenFile(self) 2051 if self._delete_on_close: 2052 self._filesystem.RemoveObject(self.name) 2053 2054 def flush(self): 2055 """Flush file contents to 'disk'.""" 2056 if self._update: 2057 self._file_object.SetContents(self._io.getvalue()) 2058 self._file_epoch = self._file_object.epoch 2059 2060 def seek(self, offset, whence=0): 2061 """Move read/write pointer in 'file'.""" 2062 if not self._append: 2063 self._io.seek(offset, whence) 2064 else: 2065 self._read_seek = offset 2066 self._read_whence = whence 2067 2068 def tell(self): 2069 """Return the file's current position. 2070 2071 Returns: 2072 int, file's current position in bytes. 2073 """ 2074 if not self._append: 2075 return self._io.tell() 2076 if self._read_whence: 2077 write_seek = self._io.tell() 2078 self._io.seek(self._read_seek, self._read_whence) 2079 self._read_seek = self._io.tell() 2080 self._read_whence = 0 2081 self._io.seek(write_seek) 2082 return self._read_seek 2083 2084 def _UpdateStringIO(self): 2085 """Updates the StringIO with changes to the file object contents.""" 2086 if self._file_epoch == self._file_object.epoch: 2087 return 2088 whence = self._io.tell() 2089 self._io.seek(0) 2090 self._io.truncate() 2091 self._io.write(self._file_object.contents) 2092 self._io.seek(whence) 2093 self._file_epoch = self._file_object.epoch 2094 2095 def _ReadWrappers(self, name): 2096 """Wrap a StringIO attribute in a read wrapper. 2097 2098 Returns a read_wrapper which tracks our own read pointer since the 2099 StringIO object has no concept of a different read and write pointer. 2100 2101 Args: 2102 name: the name StringIO attribute to wrap. Should be a read call. 2103 2104 Returns: 2105 either a read_error or read_wrapper function. 2106 """ 2107 io_attr = getattr(self._io, name) 2108 2109 def read_wrapper(*args, **kwargs): 2110 """Wrap all read calls to the StringIO Object. 2111 2112 We do this to track the read pointer separate from the write 2113 pointer. Anything that wants to read from the StringIO object 2114 while we're in append mode goes through this. 2115 2116 Args: 2117 *args: pass through args 2118 **kwargs: pass through kwargs 2119 Returns: 2120 Wrapped StringIO object method 2121 """ 2122 self._io.seek(self._read_seek, self._read_whence) 2123 ret_value = io_attr(*args, **kwargs) 2124 self._read_seek = self._io.tell() 2125 self._read_whence = 0 2126 self._io.seek(0, 2) 2127 return ret_value 2128 return read_wrapper 2129 2130 def _OtherWrapper(self, name): 2131 """Wrap a StringIO attribute in an other_wrapper. 2132 2133 Args: 2134 name: the name of the StringIO attribute to wrap. 2135 2136 Returns: 2137 other_wrapper which is described below. 2138 """ 2139 io_attr = getattr(self._io, name) 2140 2141 def other_wrapper(*args, **kwargs): 2142 """Wrap all other calls to the StringIO Object. 2143 2144 We do this to track changes to the write pointer. Anything that 2145 moves the write pointer in a file open for appending should move 2146 the read pointer as well. 2147 2148 Args: 2149 *args: pass through args 2150 **kwargs: pass through kwargs 2151 Returns: 2152 Wrapped StringIO object method 2153 """ 2154 write_seek = self._io.tell() 2155 ret_value = io_attr(*args, **kwargs) 2156 if write_seek != self._io.tell(): 2157 self._read_seek = self._io.tell() 2158 self._read_whence = 0 2159 self._file_object.st_size += (self._read_seek - write_seek) 2160 return ret_value 2161 return other_wrapper 2162 2163 def Size(self): 2164 return self._file_object.st_size 2165 2166 def __getattr__(self, name): 2167 if self._file_object.IsLargeFile(): 2168 raise FakeLargeFileIoException(file_path) 2169 2170 # errors on called method vs. open mode 2171 if not self._read and name.startswith('read'): 2172 def read_error(*args, **kwargs): 2173 """Throw an error unless the argument is zero.""" 2174 if args and args[0] == 0: 2175 return '' 2176 raise self._OPERATION_ERROR('File is not open for reading.') 2177 return read_error 2178 if not self._update and (name.startswith('write') 2179 or name == 'truncate'): 2180 def write_error(*args, **kwargs): 2181 """Throw an error.""" 2182 raise self._OPERATION_ERROR('File is not open for writing.') 2183 return write_error 2184 2185 if name.startswith('read'): 2186 self._UpdateStringIO() 2187 if self._append: 2188 if name.startswith('read'): 2189 return self._ReadWrappers(name) 2190 else: 2191 return self._OtherWrapper(name) 2192 return getattr(self._io, name) 2193 2194 def __iter__(self): 2195 if not self._read: 2196 raise self._OPERATION_ERROR('File is not open for reading') 2197 return self._io.__iter__() 2198 2199 # if you print obj.name, the argument to open() must be printed. Not the 2200 # abspath, not the filename, but the actual argument. 2201 file_object.opened_as = file_path 2202 2203 fakefile = FakeFileWrapper(file_object, 2204 update=need_write, 2205 read=need_read, 2206 append=append, 2207 delete_on_close=self._delete_on_close, 2208 filesystem=self.filesystem, 2209 newline=newline, 2210 binary=binary, 2211 closefd=closefd) 2212 if filedes is not None: 2213 fakefile.filedes = filedes 2214 else: 2215 fakefile.filedes = self.filesystem.AddOpenFile(fakefile) 2216 return fakefile 2217 2218 2219def _RunDoctest(): 2220 # pylint: disable-msg=C6204 2221 import doctest 2222 from pyfakefs import fake_filesystem # pylint: disable-msg=W0406 2223 return doctest.testmod(fake_filesystem) 2224 2225 2226if __name__ == '__main__': 2227 _RunDoctest() 2228