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