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"""A fake shutil module implementation that uses fake_filesystem for 16unit tests. 17Note that only `shutildisk_usage()` is faked, the rest of the functions shall 18work fine with the fake file system if `os`/`os.path` are patched. 19 20:Includes: 21 FakeShutil: Uses a FakeFilesystem to provide a fake replacement for the 22 shutil module. 23 24:Usage: 25 The fake implementation is automatically involved if using 26 `fake_filesystem_unittest.TestCase`, pytest fs fixture, 27 or directly `Patcher`. 28""" 29 30import os 31import shutil 32import sys 33 34 35class FakeShutilModule: 36 """Uses a FakeFilesystem to provide a fake replacement 37 for shutil module. 38 """ 39 40 @staticmethod 41 def dir(): 42 """Return the list of patched function names. Used for patching 43 functions imported from the module. 44 """ 45 return ("disk_usage",) 46 47 def __init__(self, filesystem): 48 """Construct fake shutil module using the fake filesystem. 49 50 Args: 51 filesystem: FakeFilesystem used to provide file system information 52 """ 53 self.filesystem = filesystem 54 self._shutil_module = shutil 55 56 def disk_usage(self, path): 57 """Return the total, used and free disk space in bytes as named tuple 58 or placeholder holder values simulating unlimited space if not set. 59 60 Args: 61 path: defines the filesystem device which is queried 62 """ 63 return self.filesystem.get_disk_usage(path) 64 65 if sys.version_info >= (3, 12) and sys.platform == "win32": 66 67 def copy2(self, src, dst, *, follow_symlinks=True): 68 """Since Python 3.12, there is an optimization fow Windows, 69 using the Windows API. We just remove this and fall back to the previous 70 implementation. 71 """ 72 if self.filesystem.isdir(dst): 73 dst = self.filesystem.joinpaths(dst, os.path.basename(src)) 74 75 self.copyfile(src, dst, follow_symlinks=follow_symlinks) 76 self.copystat(src, dst, follow_symlinks=follow_symlinks) 77 return dst 78 79 def copytree( 80 self, 81 src, 82 dst, 83 symlinks=False, 84 ignore=None, 85 copy_function=shutil.copy2, 86 ignore_dangling_symlinks=False, 87 dirs_exist_ok=False, 88 ): 89 """Make sure the default argument is patched.""" 90 if copy_function == shutil.copy2: 91 copy_function = self.copy2 92 return self._shutil_module.copytree( 93 src, 94 dst, 95 symlinks, 96 ignore, 97 copy_function, 98 ignore_dangling_symlinks, 99 dirs_exist_ok, 100 ) 101 102 def move(self, src, dst, copy_function=shutil.copy2): 103 """Make sure the default argument is patched.""" 104 if copy_function == shutil.copy2: 105 copy_function = self.copy2 106 return self._shutil_module.move(src, dst, copy_function) 107 108 def __getattr__(self, name): 109 """Forwards any non-faked calls to the standard shutil module.""" 110 return getattr(self._shutil_module, name) 111