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"""Test that FakeFilesystem calls work identically to a real filesystem."""
16# pylint: disable-all
17
18import os
19import shutil
20import sys
21import tempfile
22import time
23import unittest
24
25from pyfakefs import fake_filesystem, fake_os, fake_open
26from pyfakefs.helpers import IS_PYPY
27
28
29def sep(path):
30    """Converts slashes in the path to the architecture's path separator."""
31    if isinstance(path, str):
32        return path.replace("/", os.sep)
33    return path
34
35
36def _get_errno(raised_error):
37    if raised_error is not None:
38        try:
39            return raised_error.errno
40        except AttributeError:
41            pass
42
43
44class TestCase(unittest.TestCase):
45    is_windows = sys.platform.startswith("win")
46    _FAKE_FS_BASE = "C:\\fakefs" if is_windows else "/fakefs"
47
48
49class FakeFilesystemVsRealTest(TestCase):
50    def _paths(self, path):
51        """For a given path, return paths in the real and fake filesystems."""
52        if not path:
53            return None, None
54        return (
55            os.path.join(self.real_base, path),
56            os.path.join(self.fake_base, path),
57        )
58
59    def _create_test_file(self, file_type, path, contents=None):
60        """Create a dir, file, or link in both the real fs and the fake."""
61        path = sep(path)
62        self._created_files.append([file_type, path, contents])
63        real_path, fake_path = self._paths(path)
64        if file_type == "d":
65            os.mkdir(real_path)
66            self.fake_os.mkdir(fake_path)
67        if file_type == "f":
68            fh = open(real_path, "w")
69            fh.write(contents or "")
70            fh.close()
71            fh = self.fake_open(fake_path, "w")
72            fh.write(contents or "")
73            fh.close()
74        # b for binary file
75        if file_type == "b":
76            fh = open(real_path, "wb")
77            fh.write(contents or "")
78            fh.close()
79            fh = self.fake_open(fake_path, "wb")
80            fh.write(contents or "")
81            fh.close()
82        # l for symlink, h for hard link
83        if file_type in ("l", "h"):
84            real_target, fake_target = (contents, contents)
85            # If it begins with '/', make it relative to the base. You can't go
86            # creating files in / for the real file system.
87            if contents.startswith(os.sep):
88                real_target, fake_target = self._paths(contents[1:])
89            if file_type == "l":
90                os.symlink(real_target, real_path)
91                self.fake_os.symlink(fake_target, fake_path)
92            elif file_type == "h":
93                os.link(real_target, real_path)
94                self.fake_os.link(fake_target, fake_path)
95
96    def setUp(self):
97        # Base paths in the real and test file systems. We keep them different
98        # so that missing features in the fake don't fall through to the base
99        # operations and magically succeed.
100        tsname = "fakefs.%s" % time.time()
101        self.cwd = os.getcwd()
102        # Fully expand the base_path - required on OS X.
103        self.real_base = os.path.realpath(os.path.join(tempfile.gettempdir(), tsname))
104        os.chdir(tempfile.gettempdir())
105        if os.path.isdir(self.real_base):
106            shutil.rmtree(self.real_base)
107        os.mkdir(self.real_base)
108        self.fake_base = self._FAKE_FS_BASE
109
110        # Make sure we can write to the physical testing temp directory.
111        self.assertTrue(os.access(self.real_base, os.W_OK))
112
113        self.fake_filesystem = fake_filesystem.FakeFilesystem()
114        self.fake_filesystem.create_dir(self.fake_base)
115        self.fake_os = fake_os.FakeOsModule(self.fake_filesystem)
116        self.fake_open = fake_open.FakeFileOpen(self.fake_filesystem)
117        self._created_files = []
118
119        os.chdir(self.real_base)
120        self.fake_os.chdir(self.fake_base)
121
122    def tearDown(self):
123        # We have to remove all the files from the real FS. Doing the same for
124        # the fake FS is optional, but doing it is an extra sanity check.
125        os.chdir(tempfile.gettempdir())
126        try:
127            rev_files = self._created_files[:]
128            rev_files.reverse()
129            for info in rev_files:
130                real_path, fake_path = self._paths(info[1])
131                if info[0] == "d":
132                    try:
133                        os.rmdir(real_path)
134                    except OSError as e:
135                        if "Directory not empty" in e:
136                            self.fail(
137                                "Real path %s not empty: %s : %s"
138                                % (real_path, e, os.listdir(real_path))
139                            )
140                        else:
141                            raise
142                    self.fake_os.rmdir(fake_path)
143                if info[0] == "f" or info[0] == "l":
144                    os.remove(real_path)
145                    self.fake_os.remove(fake_path)
146        finally:
147            shutil.rmtree(self.real_base)
148            os.chdir(self.cwd)
149
150    def _compare_behaviors(
151        self, method_name, path, real, fake, method_returns_path=False
152    ):
153        """Invoke an os method in both real and fake contexts and compare
154        results.
155
156        Invoke a real filesystem method with a path to a real file and invoke
157        a fake filesystem method with a path to a fake file and compare the
158        results. We expect some calls to throw Exceptions, so we catch those
159        and compare them.
160
161        Args:
162            method_name: Name of method being tested, for use in
163                error messages.
164            path: potential path to a file in the real and fake file systems,
165                passing an empty tuple indicates that no arguments to pass
166                to method.
167            real: built-in system library or method from the built-in system
168                library which takes a path as an arg and returns some value.
169            fake: fake_filsystem object or method from a fake_filesystem class
170                which takes a path as an arg and returns some value.
171            method_returns_path: True if the method returns a path, and thus we
172                must compensate for expected difference between real and fake.
173
174        Returns:
175            A description of the difference in behavior, or None.
176        """
177        # pylint: disable=C6403
178
179        def _error_class(exc):
180            if exc:
181                if hasattr(exc, "errno"):
182                    return "{}({})".format(exc.__class__.__name__, exc.errno)
183                return exc.__class__.__name__
184            return "None"
185
186        real_err, real_value = self._get_real_value(method_name, path, real)
187        fake_err, fake_value = self._get_fake_value(method_name, path, fake)
188
189        method_call = f"{method_name}"
190        method_call += "()" if path == () else "({path})"
191        # We only compare on the error class because the actual error contents
192        # is almost always different because of the file paths.
193        if _error_class(real_err) != _error_class(fake_err):
194            if real_err is None:
195                return "%s: real version returned %s, fake raised %s" % (
196                    method_call,
197                    real_value,
198                    _error_class(fake_err),
199                )
200            if fake_err is None:
201                return "%s: real version raised %s, fake returned %s" % (
202                    method_call,
203                    _error_class(real_err),
204                    fake_value,
205                )
206            return "%s: real version raised %s, fake raised %s" % (
207                method_call,
208                _error_class(real_err),
209                _error_class(fake_err),
210            )
211        real_errno = _get_errno(real_err)
212        fake_errno = _get_errno(fake_err)
213        if real_errno != fake_errno:
214            return "%s(%s): both raised %s, real errno %s, fake errno %s" % (
215                method_name,
216                path,
217                _error_class(real_err),
218                real_errno,
219                fake_errno,
220            )
221        # If the method is supposed to return a full path AND both values
222        # begin with the expected full path, then trim it off.
223        if method_returns_path:
224            if (
225                real_value
226                and fake_value
227                and real_value.startswith(self.real_base)
228                and fake_value.startswith(self.fake_base)
229            ):
230                real_value = real_value[len(self.real_base) :]
231                fake_value = fake_value[len(self.fake_base) :]
232        if real_value != fake_value:
233            return "%s: real return %s, fake returned %s" % (
234                method_call,
235                real_value,
236                fake_value,
237            )
238        return None
239
240    @staticmethod
241    def _get_fake_value(method_name, path, fake):
242        fake_value = None
243        fake_err = None
244        try:
245            fake_method = fake
246            if not callable(fake):
247                fake_method = getattr(fake, method_name)
248            args = [] if path == () else [path]
249            result = fake_method(*args)
250            if isinstance(result, bytes):
251                fake_value = result.decode()
252            else:
253                fake_value = str(result)
254        except Exception as e:  # pylint: disable-msg=W0703
255            fake_err = e
256        return fake_err, fake_value
257
258    @staticmethod
259    def _get_real_value(method_name, path, real):
260        real_value = None
261        real_err = None
262        # Catching Exception below gives a lint warning, but it's what we need.
263        try:
264            args = [] if path == () else [path]
265            real_method = real
266            if not callable(real):
267                real_method = getattr(real, method_name)
268            result = real_method(*args)
269            if isinstance(result, bytes):
270                real_value = result.decode()
271            else:
272                real_value = str(result)
273        except Exception as e:  # pylint: disable-msg=W0703
274            real_err = e
275        return real_err, real_value
276
277    def assertOsMethodBehaviorMatches(
278        self, method_name, path, method_returns_path=False
279    ):
280        """Invoke an os method in both real and fake contexts and compare.
281
282        For a given method name (from the os module) and a path, compare the
283        behavior of the system provided module against the fake_filesystem
284        module.
285        We expect results and/or Exceptions raised to be identical.
286
287        Args:
288            method_name: Name of method being tested.
289            path: potential path to a file in the real and fake file systems.
290            method_returns_path: True if the method returns a path, and thus we
291                must compensate for expected difference between real and fake.
292
293        Returns:
294            A description of the difference in behavior, or None.
295        """
296        path = sep(path)
297        return self._compare_behaviors(
298            method_name, path, os, self.fake_os, method_returns_path
299        )
300
301    def diff_open_method_behavior(
302        self, method_name, path, mode, data, method_returns_data=True
303    ):
304        """Invoke an open method in both real and fkae contexts and compare.
305
306        Args:
307            method_name: Name of method being tested.
308            path: potential path to a file in the real and fake file systems.
309            mode: how to open the file.
310            data: any data to pass to the method.
311            method_returns_data: True if a method returns some sort of data.
312
313        For a given method name (from builtin open) and a path, compare the
314        behavior of the system provided module against the fake_filesystem
315        module.
316        We expect results and/or Exceptions raised to be identical.
317
318        Returns:
319            A description of the difference in behavior, or None.
320        """
321        with open(path, mode) as real_fh:
322            with self.fake_open(path, mode) as fake_fh:
323                return self._compare_behaviors(
324                    method_name, data, real_fh, fake_fh, method_returns_data
325                )
326
327    def diff_os_path_method_behavior(
328        self, method_name, path, method_returns_path=False
329    ):
330        """Invoke an os.path method in both real and fake contexts and compare.
331
332        For a given method name (from the os.path module) and a path, compare
333        the behavior of the system provided module against the
334        fake_filesytem module.
335        We expect results and/or Exceptions raised to be identical.
336
337        Args:
338            method_name: Name of method being tested.
339            path: potential path to a file in the real and fake file systems.
340            method_returns_path: True if the method returns a path, and thus we
341                must compensate for expected difference between real and fake.
342
343        Returns:
344            A description of the difference in behavior, or None.
345        """
346        return self._compare_behaviors(
347            method_name, path, os.path, self.fake_os.path, method_returns_path
348        )
349
350    def assertOsPathMethodBehaviorMatches(
351        self, method_name, path, method_returns_path=False
352    ):
353        """Assert that an os.path behaves the same in both real and
354        fake contexts.
355
356        Wraps DiffOsPathMethodBehavior, raising AssertionError if any
357        differences are reported.
358
359        Args:
360            method_name: Name of method being tested.
361            path: potential path to a file in the real and fake file systems.
362            method_returns_path: True if the method returns a path, and thus we
363                must compensate for expected difference between real and fake.
364
365        Raises:
366            AssertionError if there is any difference in behavior.
367        """
368        path = sep(path)
369        diff = self.diff_os_path_method_behavior(method_name, path, method_returns_path)
370        if diff:
371            self.fail(diff)
372
373    def assertAllOsBehaviorsMatch(self, path):
374        path = sep(path)
375        os_method_names = [] if self.is_windows else ["readlink"]
376        os_method_names_no_args = ["getcwd"]
377        os_path_method_names = ["isabs", "isdir"]
378        if not self.is_windows:
379            os_path_method_names += ["islink", "lexists"]
380        if not self.is_windows or not IS_PYPY:
381            os_path_method_names += ["isfile", "exists"]
382
383        wrapped_methods = [
384            ["access", self._access_real, self._access_fake],
385            ["stat.size", self._stat_size_real, self._stat_size_fake],
386            ["lstat.size", self._lstat_size_real, self._lstat_size_fake],
387        ]
388
389        differences = []
390        for method_name in os_method_names:
391            diff = self.assertOsMethodBehaviorMatches(method_name, path)
392            if diff:
393                differences.append(diff)
394        for method_name in os_method_names_no_args:
395            diff = self.assertOsMethodBehaviorMatches(
396                method_name, (), method_returns_path=True
397            )
398            if diff:
399                differences.append(diff)
400        for method_name in os_path_method_names:
401            diff = self.diff_os_path_method_behavior(method_name, path)
402            if diff:
403                differences.append(diff)
404        for m in wrapped_methods:
405            diff = self._compare_behaviors(m[0], path, m[1], m[2])
406            if diff:
407                differences.append(diff)
408        if differences:
409            self.fail(
410                "Behaviors do not match for %s:\n    %s"
411                % (path, "\n    ".join(differences))
412            )
413
414    def assertFileHandleBehaviorsMatch(self, path, mode, data):
415        path = sep(path)
416        write_method_names = ["write", "writelines"]
417        read_method_names = ["read", "readlines"]
418        other_method_names = ["truncate", "flush", "close"]
419        differences = []
420        for method_name in write_method_names:
421            diff = self.diff_open_method_behavior(method_name, path, mode, data)
422            if diff:
423                differences.append(diff)
424        for method_name in read_method_names + other_method_names:
425            diff = self.diff_open_method_behavior(method_name, path, mode, ())
426            if diff:
427                differences.append(diff)
428        if differences:
429            self.fail(
430                "Behaviors do not match for %s:\n    %s"
431                % (path, "\n    ".join(differences))
432            )
433
434    def assertFileHandleOpenBehaviorsMatch(self, *args, **kwargs):
435        """Compare open() function invocation between real and fake.
436
437        Runs open(*args, **kwargs) on both real and fake.
438
439        Args:
440            *args: args to pass through to open()
441            **kwargs: kwargs to pass through to open().
442
443        Returns:
444            None.
445
446        Raises:
447            AssertionError if underlying open() behavior differs from fake.
448        """
449        real_err = None
450        fake_err = None
451        try:
452            with open(*args, **kwargs):
453                pass
454        except Exception as e:  # pylint: disable-msg=W0703
455            real_err = e
456
457        try:
458            with self.fake_open(*args, **kwargs):
459                pass
460        except Exception as e:  # pylint: disable-msg=W0703
461            fake_err = e
462
463        # default equal in case one is None and other is not.
464        is_exception_equal = real_err == fake_err
465        if real_err and fake_err:
466            # exception __eq__ doesn't evaluate equal ever, thus manual check.
467            is_exception_equal = (
468                type(real_err) is type(fake_err) and real_err.args == fake_err.args
469            )
470
471        if not is_exception_equal:
472            msg = "Behaviors don't match on open with args %s & kwargs %s.\n" % (
473                args,
474                kwargs,
475            )
476            real_err_msg = "Real open results in: %s\n" % repr(real_err)
477            fake_err_msg = "Fake open results in: %s\n" % repr(fake_err)
478            self.fail(msg + real_err_msg + fake_err_msg)
479
480    # Helpers for checks which are not straight method calls.
481    @staticmethod
482    def _access_real(path):
483        return os.access(path, os.R_OK)
484
485    def _access_fake(self, path):
486        return self.fake_os.access(path, os.R_OK)
487
488    def _stat_size_real(self, path):
489        real_path, unused_fake_path = self._paths(path)
490        # fake_filesystem.py does not implement stat().st_size for directories
491        if os.path.isdir(real_path):
492            return None
493        return os.stat(real_path).st_size
494
495    def _stat_size_fake(self, path):
496        unused_real_path, fake_path = self._paths(path)
497        # fake_filesystem.py does not implement stat().st_size for directories
498        if self.fake_os.path.isdir(fake_path):
499            return None
500        return self.fake_os.stat(fake_path).st_size
501
502    def _lstat_size_real(self, path):
503        real_path, unused_fake_path = self._paths(path)
504        if os.path.isdir(real_path):
505            return None
506        size = os.lstat(real_path).st_size
507        # Account for the difference in the lengths of the absolute paths.
508        if os.path.islink(real_path):
509            if os.readlink(real_path).startswith(os.sep):
510                size -= len(self.real_base)
511        return size
512
513    def _lstat_size_fake(self, path):
514        unused_real_path, fake_path = self._paths(path)
515        # size = 0
516        if self.fake_os.path.isdir(fake_path):
517            return None
518        size = self.fake_os.lstat(fake_path).st_size
519        # Account for the difference in the lengths of the absolute paths.
520        if self.fake_os.path.islink(fake_path):
521            if self.fake_os.readlink(fake_path).startswith(os.sep):
522                size -= len(self.fake_base)
523        return size
524
525    def test_isabs(self):
526        # We do not have to create any files for isabs.
527        self.assertOsPathMethodBehaviorMatches("isabs", None)
528        self.assertOsPathMethodBehaviorMatches("isabs", "")
529        self.assertOsPathMethodBehaviorMatches("isabs", "/")
530        self.assertOsPathMethodBehaviorMatches("isabs", "/a")
531        self.assertOsPathMethodBehaviorMatches("isabs", "a")
532
533    def test_none_path(self):
534        self.assertAllOsBehaviorsMatch(None)
535
536    def test_empty_path(self):
537        self.assertAllOsBehaviorsMatch("")
538
539    def test_root_path(self):
540        self.assertAllOsBehaviorsMatch("/")
541
542    def test_non_existant_file(self):
543        self.assertAllOsBehaviorsMatch("foo")
544
545    def test_empty_file(self):
546        self._create_test_file("f", "aFile")
547        self.assertAllOsBehaviorsMatch("aFile")
548
549    def test_file_with_contents(self):
550        self._create_test_file("f", "aFile", "some contents")
551        self.assertAllOsBehaviorsMatch("aFile")
552
553    def test_file_with_binary_contents(self):
554        self._create_test_file("b", "aFile", b"some contents")
555        self.assertAllOsBehaviorsMatch("aFile")
556
557    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
558    def test_sym_link_to_empty_file(self):
559        self._create_test_file("f", "aFile")
560        self._create_test_file("l", "link_to_empty", "aFile")
561        self.assertAllOsBehaviorsMatch("link_to_empty")
562
563    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
564    def test_hard_link_to_empty_file(self):
565        self._create_test_file("f", "aFile")
566        self._create_test_file("h", "link_to_empty", "aFile")
567        self.assertAllOsBehaviorsMatch("link_to_empty")
568
569    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
570    def test_sym_link_to_real_file(self):
571        self._create_test_file("f", "aFile", "some contents")
572        self._create_test_file("l", "link_to_file", "aFile")
573        self.assertAllOsBehaviorsMatch("link_to_file")
574
575    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
576    def test_hard_link_to_real_file(self):
577        self._create_test_file("f", "aFile", "some contents")
578        self._create_test_file("h", "link_to_file", "aFile")
579        self.assertAllOsBehaviorsMatch("link_to_file")
580
581    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
582    def test_broken_sym_link(self):
583        self._create_test_file("l", "broken_link", "broken")
584        self._create_test_file("l", "loop", "/a/loop")
585        self.assertAllOsBehaviorsMatch("broken_link")
586
587    def test_file_in_a_folder(self):
588        self._create_test_file("d", "a")
589        self._create_test_file("d", "a/b")
590        self._create_test_file("f", "a/b/file", "contents")
591        self.assertAllOsBehaviorsMatch("a/b/file")
592
593    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
594    def test_absolute_sym_link_to_folder(self):
595        self._create_test_file("d", "a")
596        self._create_test_file("d", "a/b")
597        self._create_test_file("f", "a/b/file", "contents")
598        self._create_test_file("l", "a/link", "/a/b")
599        self.assertAllOsBehaviorsMatch("a/link/file")
600
601    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
602    def test_link_to_folder_after_chdir(self):
603        self._create_test_file("d", "a")
604        self._create_test_file("d", "a/b")
605        self._create_test_file("f", "a/b/file", "contents")
606        self._create_test_file("l", "a/link", "/a/b")
607
608        real_dir, fake_dir = self._paths("a/b")
609        os.chdir(real_dir)
610        self.fake_os.chdir(fake_dir)
611        self.assertAllOsBehaviorsMatch("file")
612
613    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
614    def test_relative_sym_link_to_folder(self):
615        self._create_test_file("d", "a")
616        self._create_test_file("d", "a/b")
617        self._create_test_file("f", "a/b/file", "contents")
618        self._create_test_file("l", "a/link", "b")
619        self.assertAllOsBehaviorsMatch("a/link/file")
620
621    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
622    def test_sym_link_to_parent(self):
623        # Soft links on HFS+ / OS X behave differently.
624        if os.uname()[0] != "Darwin":
625            self._create_test_file("d", "a")
626            self._create_test_file("d", "a/b")
627            self._create_test_file("l", "a/b/c", "..")
628            self.assertAllOsBehaviorsMatch("a/b/c")
629
630    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
631    def test_path_through_sym_link_to_parent(self):
632        self._create_test_file("d", "a")
633        self._create_test_file("f", "a/target", "contents")
634        self._create_test_file("d", "a/b")
635        self._create_test_file("l", "a/b/c", "..")
636        self.assertAllOsBehaviorsMatch("a/b/c/target")
637
638    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
639    def test_sym_link_to_sibling_directory(self):
640        self._create_test_file("d", "a")
641        self._create_test_file("d", "a/b")
642        self._create_test_file("d", "a/sibling_of_b")
643        self._create_test_file("f", "a/sibling_of_b/target", "contents")
644        self._create_test_file("l", "a/b/c", "../sibling_of_b")
645        self.assertAllOsBehaviorsMatch("a/b/c/target")
646
647    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
648    def test_sym_link_to_sibling_directory_non_existant_file(self):
649        self._create_test_file("d", "a")
650        self._create_test_file("d", "a/b")
651        self._create_test_file("d", "a/sibling_of_b")
652        self._create_test_file("f", "a/sibling_of_b/target", "contents")
653        self._create_test_file("l", "a/b/c", "../sibling_of_b")
654        self.assertAllOsBehaviorsMatch("a/b/c/file_does_not_exist")
655
656    @unittest.skipIf(TestCase.is_windows, "no symlink in Windows")
657    def test_broken_sym_link_to_sibling_directory(self):
658        self._create_test_file("d", "a")
659        self._create_test_file("d", "a/b")
660        self._create_test_file("d", "a/sibling_of_b")
661        self._create_test_file("f", "a/sibling_of_b/target", "contents")
662        self._create_test_file("l", "a/b/c", "../broken_sibling_of_b")
663        self.assertAllOsBehaviorsMatch("a/b/c/target")
664
665    def test_relative_path(self):
666        self._create_test_file("d", "a")
667        self._create_test_file("d", "a/b")
668        self._create_test_file("d", "a/sibling_of_b")
669        self._create_test_file("f", "a/sibling_of_b/target", "contents")
670        self.assertAllOsBehaviorsMatch("a/b/../sibling_of_b/target")
671
672    def test_broken_relative_path(self):
673        self._create_test_file("d", "a")
674        self._create_test_file("d", "a/b")
675        self._create_test_file("d", "a/sibling_of_b")
676        self._create_test_file("f", "a/sibling_of_b/target", "contents")
677        self.assertAllOsBehaviorsMatch("a/b/../broken/target")
678
679    def test_bad_relative_path(self):
680        self._create_test_file("d", "a")
681        self._create_test_file("f", "a/target", "contents")
682        self._create_test_file("d", "a/b")
683        self._create_test_file("d", "a/sibling_of_b")
684        self._create_test_file("f", "a/sibling_of_b/target", "contents")
685        self.assertAllOsBehaviorsMatch("a/b/../broken/../target")
686
687    def test_getmtime_nonexistant_path(self):
688        self.assertOsPathMethodBehaviorMatches("getmtime", "no/such/path")
689
690    def test_builtin_open_modes(self):
691        self._create_test_file("f", "read", "some contents")
692        self._create_test_file("f", "write", "some contents")
693        self._create_test_file("f", "append", "some contents")
694        self.assertFileHandleBehaviorsMatch("read", "r", "other contents")
695        self.assertFileHandleBehaviorsMatch("write", "w", "other contents")
696        self.assertFileHandleBehaviorsMatch("append", "a", "other contents")
697        self._create_test_file("f", "readplus", "some contents")
698        self._create_test_file("f", "writeplus", "some contents")
699        self.assertFileHandleBehaviorsMatch("readplus", "r+", "other contents")
700        self.assertFileHandleBehaviorsMatch("writeplus", "w+", "other contents")
701        self._create_test_file("b", "binaryread", b"some contents")
702        self._create_test_file("b", "binarywrite", b"some contents")
703        self._create_test_file("b", "binaryappend", b"some contents")
704        self.assertFileHandleBehaviorsMatch("binaryread", "rb", b"other contents")
705        self.assertFileHandleBehaviorsMatch("binarywrite", "wb", b"other contents")
706        self.assertFileHandleBehaviorsMatch("binaryappend", "ab", b"other contents")
707        self.assertFileHandleBehaviorsMatch("read", "rb", "other contents")
708        self.assertFileHandleBehaviorsMatch("write", "wb", "other contents")
709        self.assertFileHandleBehaviorsMatch("append", "ab", "other contents")
710
711        # binary cannot have encoding
712        self.assertFileHandleOpenBehaviorsMatch("read", "rb", encoding="enc")
713        self.assertFileHandleOpenBehaviorsMatch("write", mode="wb", encoding="enc")
714        self.assertFileHandleOpenBehaviorsMatch("append", "ab", encoding="enc")
715
716        # text can have encoding
717        self.assertFileHandleOpenBehaviorsMatch("read", "r", encoding="utf-8")
718        self.assertFileHandleOpenBehaviorsMatch("write", "w", encoding="utf-8")
719        self.assertFileHandleOpenBehaviorsMatch("append", "a", encoding="utf-8")
720
721
722def main(_):
723    unittest.main()
724
725
726if __name__ == "__main__":
727    unittest.main()
728