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