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