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"""Tests for `fake_filesystem_shutil` if used in
16`fake_filesystem_unittest.TestCase`.
17Note that almost all of the functionality is delegated to the real `shutil`
18and works correctly with the fake filesystem because of the faked `os` module.
19"""
20import os
21import shutil
22import sys
23import tempfile
24import unittest
25from pathlib import Path
26
27from pyfakefs import fake_filesystem_unittest
28from pyfakefs.helpers import get_uid, set_uid, is_root, IS_PYPY
29from pyfakefs.tests.test_utils import RealFsTestMixin
30
31is_windows = sys.platform == "win32"
32
33
34class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin):
35    def __init__(self, methodName="runTest"):
36        fake_filesystem_unittest.TestCase.__init__(self, methodName)
37        RealFsTestMixin.__init__(self)
38
39    def setUp(self):
40        RealFsTestMixin.setUp(self)
41        self.cwd = os.getcwd()
42        self.uid = get_uid()
43        set_uid(1000)
44        if not self.use_real_fs():
45            self.setUpPyfakefs()
46            self.filesystem = self.fs
47            self.os = os
48            self.open = open
49            self.create_basepath()
50            self.fs.set_disk_usage(1000, self.base_path)
51
52    def tearDown(self):
53        set_uid(self.uid)
54        RealFsTestMixin.tearDown(self)
55
56    @property
57    def is_windows_fs(self):
58        if self.use_real_fs():
59            return sys.platform == "win32"
60        return self.filesystem.is_windows_fs
61
62
63class FakeShutilModuleTest(RealFsTestCase):
64    @unittest.skipIf(is_windows, "Posix specific behavior")
65    def test_catch_permission_error(self):
66        root_path = self.make_path("rootpath")
67        self.create_dir(root_path)
68        dir1_path = self.os.path.join(root_path, "dir1")
69        dir2_path = self.os.path.join(root_path, "dir2")
70        self.create_dir(dir1_path)
71        self.os.chmod(dir1_path, 0o555)  # remove write permissions
72        self.create_dir(dir2_path)
73        old_file_path = self.os.path.join(dir2_path, "f1.txt")
74        new_file_path = self.os.path.join(dir1_path, "f1.txt")
75        self.create_file(old_file_path)
76
77        with self.assertRaises(PermissionError):
78            shutil.move(old_file_path, new_file_path)
79
80    def test_rmtree(self):
81        directory = self.make_path("xyzzy")
82        dir_path = os.path.join(directory, "subdir")
83        self.create_dir(dir_path)
84        file_path = os.path.join(directory, "subfile")
85        self.create_file(file_path)
86        self.assertTrue(os.path.exists(directory))
87        shutil.rmtree(directory)
88        self.assertFalse(os.path.exists(directory))
89        self.assertFalse(os.path.exists(dir_path))
90        self.assertFalse(os.path.exists(file_path))
91
92    def test_rmtree_with_trailing_slash(self):
93        directory = self.make_path("xyzzy")
94        dir_path = os.path.join(directory, "subdir")
95        self.create_dir(dir_path)
96        file_path = os.path.join(directory, "subfile")
97        self.create_file(file_path)
98        shutil.rmtree(directory + "/")
99        self.assertFalse(os.path.exists(directory))
100        self.assertFalse(os.path.exists(dir_path))
101        self.assertFalse(os.path.exists(file_path))
102
103    @unittest.skipIf(not is_windows, "Windows specific behavior")
104    def test_rmtree_without_permission_for_a_file_in_windows(self):
105        self.check_windows_only()
106        dir_path = self.make_path("foo")
107        self.create_file(os.path.join(dir_path, "bar"))
108        file_path = os.path.join(dir_path, "baz")
109        self.create_file(file_path)
110        self.os.chmod(file_path, 0o444)
111        with self.assertRaises(OSError):
112            shutil.rmtree(dir_path)
113        self.assertTrue(os.path.exists(file_path))
114        self.os.chmod(file_path, 0o666)
115
116    @unittest.skipIf(is_windows, "Posix specific behavior")
117    def test_rmtree_without_permission_for_a_dir_in_posix(self):
118        self.check_posix_only()
119        dir_path = self.make_path("foo")
120        self.create_file(os.path.join(dir_path, "bar"))
121        file_path = os.path.join(dir_path, "baz")
122        self.create_file(file_path)
123        self.os.chmod(dir_path, 0o555)
124        if not is_root():
125            with self.assertRaises(OSError):
126                shutil.rmtree(dir_path)
127            self.assertTrue(os.path.exists(file_path))
128            self.os.chmod(dir_path, 0o777)
129        else:
130            shutil.rmtree(dir_path)
131            self.assertFalse(os.path.exists(file_path))
132
133    @unittest.skipIf(is_windows, "Posix specific behavior")
134    def test_rmtree_with_open_file_posix(self):
135        self.check_posix_only()
136        dir_path = self.make_path("foo")
137        self.create_file(os.path.join(dir_path, "bar"))
138        file_path = os.path.join(dir_path, "baz")
139        self.create_file(file_path)
140        with open(file_path):
141            shutil.rmtree(dir_path)
142        self.assertFalse(os.path.exists(file_path))
143
144    @unittest.skipIf(not is_windows, "Windows specific behavior")
145    def test_rmtree_with_open_file_fails_under_windows(self):
146        self.check_windows_only()
147        dir_path = self.make_path("foo")
148        self.create_file(os.path.join(dir_path, "bar"))
149        file_path = os.path.join(dir_path, "baz")
150        self.create_file(file_path)
151        with open(file_path):
152            with self.assertRaises(OSError):
153                shutil.rmtree(dir_path)
154        self.assertTrue(os.path.exists(dir_path))
155
156    def test_rmtree_non_existing_dir(self):
157        directory = "nonexisting"
158        with self.assertRaises(OSError):
159            shutil.rmtree(directory)
160        try:
161            shutil.rmtree(directory, ignore_errors=True)
162        except OSError:
163            self.fail("rmtree raised despite ignore_errors True")
164
165    def test_rmtree_non_existing_dir_with_handler(self):
166        class NonLocal:
167            pass
168
169        def error_handler(_, path, _error_info):
170            NonLocal.errorHandled = True
171            NonLocal.errorPath = path
172
173        directory = self.make_path("nonexisting")
174        NonLocal.errorHandled = False
175        NonLocal.errorPath = ""
176        try:
177            shutil.rmtree(directory, onerror=error_handler)
178        except OSError:
179            self.fail("rmtree raised exception despite onerror defined")
180        self.assertTrue(NonLocal.errorHandled)
181        self.assertEqual(NonLocal.errorPath, directory)
182
183        NonLocal.errorHandled = False
184        NonLocal.errorPath = ""
185        try:
186            shutil.rmtree(directory, ignore_errors=True, onerror=error_handler)
187        except OSError:
188            self.fail("rmtree raised exception despite ignore_errors True")
189        # ignore_errors is True, so the onerror() error handler was
190        # not executed
191        self.assertFalse(NonLocal.errorHandled)
192        self.assertEqual(NonLocal.errorPath, "")
193
194    def test_copy(self):
195        src_file = self.make_path("xyzzy")
196        dst_file = self.make_path("xyzzy_copy")
197        self.create_file(src_file)
198        os.chmod(src_file, 0o750)
199        self.assertTrue(os.path.exists(src_file))
200        self.assertFalse(os.path.exists(dst_file))
201        shutil.copy(src_file, dst_file)
202        self.assertTrue(os.path.exists(dst_file))
203        self.assertEqual(os.stat(src_file).st_mode, os.stat(dst_file).st_mode)
204
205    def test_copy_directory(self):
206        src_file = self.make_path("xyzzy")
207        parent_directory = self.make_path("parent")
208        dst_file = os.path.join(parent_directory, "xyzzy")
209        self.create_file(src_file)
210        self.create_dir(parent_directory)
211        os.chmod(src_file, 0o750)
212        self.assertTrue(os.path.exists(src_file))
213        self.assertTrue(os.path.exists(parent_directory))
214        self.assertFalse(os.path.exists(dst_file))
215        shutil.copy(src_file, parent_directory)
216        self.assertTrue(os.path.exists(dst_file))
217        self.assertEqual(os.stat(src_file).st_mode, os.stat(dst_file).st_mode)
218
219    def test_copystat(self):
220        src_file = self.make_path("xyzzy")
221        self.create_file(src_file)
222        os.chmod(src_file, 0o750)
223        dst_file = self.make_path("xyzzy_copy")
224        self.create_file(dst_file)
225        self.assertTrue(os.path.exists(src_file))
226        self.assertTrue(os.path.exists(dst_file))
227        shutil.copystat(src_file, dst_file)
228        src_stat = os.stat(src_file)
229        dst_stat = os.stat(dst_file)
230        self.assertEqual(src_stat.st_mode, dst_stat.st_mode)
231        self.assertAlmostEqual(src_stat.st_atime, dst_stat.st_atime, places=2)
232        self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2)
233
234    @unittest.skipIf(IS_PYPY, "Functionality not supported in PyPy")
235    def test_copystat_symlinks(self):
236        """Regression test for #799"""
237        self.skip_if_symlink_not_supported()
238        f = self.make_path("xyzzy")
239        self.create_file(f)
240        sym1 = self.make_path("sym1")
241        sym2 = self.make_path("sym2")
242        self.create_symlink(sym1, f)
243        self.create_symlink(sym2, f)
244        shutil.copystat(sym1, sym2, follow_symlinks=False)
245
246    def test_copy2(self):
247        src_file = self.make_path("xyzzy")
248        self.create_file(src_file)
249        os.chmod(src_file, 0o750)
250        dst_file = self.make_path("xyzzy_copy")
251        self.assertTrue(os.path.exists(src_file))
252        self.assertFalse(os.path.exists(dst_file))
253        shutil.copy2(src_file, dst_file)
254        self.assertTrue(os.path.exists(dst_file))
255        src_stat = os.stat(src_file)
256        dst_stat = os.stat(dst_file)
257        self.assertEqual(src_stat.st_mode, dst_stat.st_mode)
258        self.assertAlmostEqual(src_stat.st_atime, dst_stat.st_atime, places=2)
259        self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2)
260
261    def test_copy2_directory(self):
262        src_file = self.make_path("xyzzy")
263        parent_directory = self.make_path("parent")
264        dst_file = os.path.join(parent_directory, "xyzzy")
265        self.create_file(src_file)
266        self.create_dir(parent_directory)
267        os.chmod(src_file, 0o750)
268        self.assertTrue(os.path.exists(src_file))
269        self.assertTrue(os.path.exists(parent_directory))
270        self.assertFalse(os.path.exists(dst_file))
271        shutil.copy2(src_file, parent_directory)
272        self.assertTrue(os.path.exists(dst_file))
273        src_stat = os.stat(src_file)
274        dst_stat = os.stat(dst_file)
275        self.assertEqual(src_stat.st_mode, dst_stat.st_mode)
276        self.assertAlmostEqual(src_stat.st_atime, dst_stat.st_atime, places=2)
277        self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2)
278
279    def test_copytree(self):
280        src_directory = self.make_path("xyzzy")
281        dst_directory = self.make_path("xyzzy_copy")
282        self.create_dir(src_directory)
283        self.create_dir("%s/subdir" % src_directory)
284        self.create_file(os.path.join(src_directory, "subfile"))
285        self.assertTrue(os.path.exists(src_directory))
286        self.assertFalse(os.path.exists(dst_directory))
287        shutil.copytree(src_directory, dst_directory)
288        self.assertTrue(os.path.exists(dst_directory))
289        self.assertTrue(os.path.exists(os.path.join(dst_directory, "subdir")))
290        self.assertTrue(os.path.exists(os.path.join(dst_directory, "subfile")))
291
292    def test_copytree_src_is_file(self):
293        src_file = self.make_path("xyzzy")
294        dst_directory = self.make_path("xyzzy_copy")
295        self.create_file(src_file)
296        self.assertTrue(os.path.exists(src_file))
297        self.assertFalse(os.path.exists(dst_directory))
298        with self.assertRaises(OSError):
299            shutil.copytree(src_file, dst_directory)
300
301    def test_move_file_in_same_filesystem(self):
302        self.skip_real_fs()
303        src_file = "/original_xyzzy"
304        dst_file = "/moved_xyzzy"
305        src_object = self.fs.create_file(src_file)
306        src_ino = src_object.st_ino
307        src_dev = src_object.st_dev
308
309        self.assertTrue(os.path.exists(src_file))
310        self.assertFalse(os.path.exists(dst_file))
311        shutil.move(src_file, dst_file)
312        self.assertTrue(os.path.exists(dst_file))
313        self.assertFalse(os.path.exists(src_file))
314
315        dst_object = self.fs.get_object(dst_file)
316        self.assertEqual(src_ino, dst_object.st_ino)
317        self.assertEqual(src_dev, dst_object.st_dev)
318
319    def test_move_file_into_other_filesystem(self):
320        self.skip_real_fs()
321        mount_point = self.create_mount_point()
322
323        src_file = self.make_path("original_xyzzy")
324        dst_file = self.os.path.join(mount_point, "moved_xyzzy")
325        src_object = self.fs.create_file(src_file)
326        src_ino = src_object.st_ino
327        src_dev = src_object.st_dev
328
329        shutil.move(src_file, dst_file)
330        self.assertTrue(os.path.exists(dst_file))
331        self.assertFalse(os.path.exists(src_file))
332
333        dst_object = self.fs.get_object(dst_file)
334        self.assertNotEqual(src_ino, dst_object.st_ino)
335        self.assertNotEqual(src_dev, dst_object.st_dev)
336
337    def test_move_file_into_directory(self):
338        src_file = self.make_path("xyzzy")
339        dst_directory = self.make_path("directory")
340        dst_file = os.path.join(dst_directory, "xyzzy")
341        self.create_file(src_file)
342        self.create_dir(dst_directory)
343        self.assertTrue(os.path.exists(src_file))
344        self.assertFalse(os.path.exists(dst_file))
345        shutil.move(src_file, dst_directory)
346        self.assertTrue(os.path.exists(dst_file))
347        self.assertFalse(os.path.exists(src_file))
348
349    def test_move_directory(self):
350        src_directory = self.make_path("original_xyzzy")
351        dst_directory = self.make_path("moved_xyzzy")
352        self.create_dir(src_directory)
353        self.create_file(os.path.join(src_directory, "subfile"))
354        self.create_dir(os.path.join(src_directory, "subdir"))
355        self.assertTrue(os.path.exists(src_directory))
356        self.assertFalse(os.path.exists(dst_directory))
357        shutil.move(src_directory, dst_directory)
358        self.assertTrue(os.path.exists(dst_directory))
359        self.assertTrue(os.path.exists(os.path.join(dst_directory, "subfile")))
360        self.assertTrue(os.path.exists(os.path.join(dst_directory, "subdir")))
361        self.assertFalse(os.path.exists(src_directory))
362
363    def test_disk_usage(self):
364        self.skip_real_fs()
365        file_path = self.make_path("foo", "bar")
366        self.fs.create_file(file_path, st_size=400)
367        disk_usage = shutil.disk_usage(file_path)
368        self.assertEqual(1000, disk_usage.total)
369        self.assertEqual(400, disk_usage.used)
370        self.assertEqual(600, disk_usage.free)
371        self.assertEqual((1000, 400, 600), disk_usage)
372
373        mount_point = self.create_mount_point()
374        dir_path = self.os.path.join(mount_point, "foo")
375        file_path = self.os.path.join(dir_path, "bar")
376        self.fs.create_file(file_path, st_size=400)
377        disk_usage = shutil.disk_usage(dir_path)
378        self.assertEqual((500, 400, 100), disk_usage)
379
380    def test_disk_usage_with_path(self):
381        self.skip_real_fs()
382        file_path = self.make_path("foo", "bar")
383        self.fs.create_file(file_path, st_size=400)
384        path = Path(file_path)
385        disk_usage = shutil.disk_usage(path)
386        self.assertEqual(1000, disk_usage.total)
387        self.assertEqual(400, disk_usage.used)
388        self.assertEqual(600, disk_usage.free)
389        self.assertEqual((1000, 400, 600), disk_usage)
390
391    def create_mount_point(self):
392        mount_point = "M:" if self.is_windows_fs else "/mount"
393        self.fs.add_mount_point(mount_point, total_size=500)
394        return mount_point
395
396
397class RealShutilModuleTest(FakeShutilModuleTest):
398    def use_real_fs(self):
399        return True
400
401
402class FakeCopyFileTest(RealFsTestCase):
403    def tearDown(self):
404        super(FakeCopyFileTest, self).tearDown()
405
406    def test_common_case(self):
407        src_file = self.make_path("xyzzy")
408        dst_file = self.make_path("xyzzy_copy")
409        contents = "contents of file"
410        self.create_file(src_file, contents=contents)
411        self.assertTrue(os.path.exists(src_file))
412        self.assertFalse(os.path.exists(dst_file))
413        shutil.copyfile(src_file, dst_file)
414        self.assertTrue(os.path.exists(dst_file))
415        self.check_contents(dst_file, contents)
416
417    def test_raises_if_source_and_dest_are_the_same_file(self):
418        src_file = self.make_path("xyzzy")
419        dst_file = src_file
420        contents = "contents of file"
421        self.create_file(src_file, contents=contents)
422        self.assertTrue(os.path.exists(src_file))
423        with self.assertRaises(shutil.Error):
424            shutil.copyfile(src_file, dst_file)
425
426    def test_raises_if_dest_is_a_symlink_to_src(self):
427        self.skip_if_symlink_not_supported()
428        src_file = self.make_path("foo")
429        dst_file = self.make_path("bar")
430        contents = "contents of file"
431        self.create_file(src_file, contents=contents)
432        self.create_symlink(dst_file, src_file)
433        self.assertTrue(os.path.exists(src_file))
434        with self.assertRaises(shutil.Error):
435            shutil.copyfile(src_file, dst_file)
436
437    def test_succeeds_if_dest_exists_and_is_writable(self):
438        src_file = self.make_path("xyzzy")
439        dst_file = self.make_path("xyzzy_copy")
440        src_contents = "contents of source file"
441        dst_contents = "contents of dest file"
442        self.create_file(src_file, contents=src_contents)
443        self.create_file(dst_file, contents=dst_contents)
444        self.assertTrue(os.path.exists(src_file))
445        self.assertTrue(os.path.exists(dst_file))
446        shutil.copyfile(src_file, dst_file)
447        self.assertTrue(os.path.exists(dst_file))
448        self.check_contents(dst_file, src_contents)
449
450    def test_raises_if_dest_exists_and_is_not_writable(self):
451        src_file = self.make_path("xyzzy")
452        dst_file = self.make_path("xyzzy_copy")
453        src_contents = "contents of source file"
454        dst_contents = "contents of dest file"
455        self.create_file(src_file, contents=src_contents)
456        self.create_file(dst_file, contents=dst_contents)
457        os.chmod(dst_file, 0o400)
458        self.assertTrue(os.path.exists(src_file))
459        self.assertTrue(os.path.exists(dst_file))
460
461        if is_root():
462            shutil.copyfile(src_file, dst_file)
463            self.assertTrue(self.os.path.exists(dst_file))
464            with self.open(dst_file) as f:
465                self.assertEqual("contents of source file", f.read())
466        else:
467            with self.assertRaises(OSError):
468                shutil.copyfile(src_file, dst_file)
469
470        os.chmod(dst_file, 0o666)
471
472    @unittest.skipIf(is_windows, "Posix specific behavior")
473    def test_raises_if_dest_dir_is_not_writable_under_posix(self):
474        self.check_posix_only()
475        src_file = self.make_path("xyzzy")
476        dst_dir = self.make_path("tmp", "foo")
477        dst_file = os.path.join(dst_dir, "xyzzy")
478        src_contents = "contents of source file"
479        self.create_file(src_file, contents=src_contents)
480        self.create_dir(dst_dir)
481        os.chmod(dst_dir, 0o555)
482        self.assertTrue(os.path.exists(src_file))
483        self.assertTrue(os.path.exists(dst_dir))
484        if not is_root():
485            with self.assertRaises(OSError):
486                shutil.copyfile(src_file, dst_file)
487        else:
488            shutil.copyfile(src_file, dst_file)
489            self.assertTrue(os.path.exists(dst_file))
490            self.check_contents(dst_file, src_contents)
491
492    def test_raises_if_src_doesnt_exist(self):
493        src_file = self.make_path("xyzzy")
494        dst_file = self.make_path("xyzzy_copy")
495        self.assertFalse(os.path.exists(src_file))
496        with self.assertRaises(OSError):
497            shutil.copyfile(src_file, dst_file)
498
499    @unittest.skipIf(is_windows, "Posix specific behavior")
500    def test_raises_if_src_not_readable(self):
501        self.check_posix_only()
502        src_file = self.make_path("xyzzy")
503        dst_file = self.make_path("xyzzy_copy")
504        src_contents = "contents of source file"
505        self.create_file(src_file, contents=src_contents)
506        os.chmod(src_file, 0o000)
507        self.assertTrue(os.path.exists(src_file))
508        if not is_root():
509            with self.assertRaises(OSError):
510                shutil.copyfile(src_file, dst_file)
511        else:
512            shutil.copyfile(src_file, dst_file)
513            self.assertTrue(os.path.exists(dst_file))
514            self.check_contents(dst_file, src_contents)
515
516    def test_raises_if_src_is_a_directory(self):
517        src_file = self.make_path("xyzzy")
518        dst_file = self.make_path("xyzzy_copy")
519        self.create_dir(src_file)
520        self.assertTrue(os.path.exists(src_file))
521        with self.assertRaises(OSError):
522            shutil.copyfile(src_file, dst_file)
523
524    def test_raises_if_dest_is_a_directory(self):
525        src_file = self.make_path("xyzzy")
526        dst_dir = self.make_path("tmp", "foo")
527        src_contents = "contents of source file"
528        self.create_file(src_file, contents=src_contents)
529        self.create_dir(dst_dir)
530        self.assertTrue(os.path.exists(src_file))
531        self.assertTrue(os.path.exists(dst_dir))
532        with self.assertRaises(OSError):
533            shutil.copyfile(src_file, dst_dir)
534
535    def test_moving_dir_into_dir(self):
536        # regression test for #515
537        source_dir = tempfile.mkdtemp()
538        target_dir = tempfile.mkdtemp()
539        filename = "foo.pdf"
540        with open(os.path.join(source_dir, filename), "wb") as fp:
541            fp.write(b"stub")
542
543        shutil.move(source_dir, target_dir)
544        shutil.rmtree(target_dir)
545
546
547class RealCopyFileTest(FakeCopyFileTest):
548    def use_real_fs(self):
549        return True
550
551
552if __name__ == "__main__":
553    unittest.main()
554