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