2010-11-04 Dirk Pranke <dpranke@chromium.org>
authordpranke@chromium.org <dpranke@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 5 Nov 2010 02:51:58 +0000 (02:51 +0000)
committerdpranke@chromium.org <dpranke@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 5 Nov 2010 02:51:58 +0000 (02:51 +0000)
        Reviewed by Adam Barth.

        Create a filesystem wrapper that we can use to enforce
        particular conventions and use for mocking and dependency
        injection down the line.

        https://bugs.webkit.org/show_bug.cgi?id=48144

        * Scripts/webkitpy/common/system/filesystem.py: Added.
        * Scripts/webkitpy/common/system/filesystem_unittest.py: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@71383 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebKitTools/ChangeLog
WebKitTools/Scripts/webkitpy/common/system/filesystem.py [new file with mode: 0644]
WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py [new file with mode: 0644]

index be7eb4f1e43102ff5b4cf7d8271536f57b566ff1..e8d34cbac894638f0c67b5bc835016d2f5ca107b 100644 (file)
@@ -1,3 +1,16 @@
+2010-11-04  Dirk Pranke  <dpranke@chromium.org>
+
+        Reviewed by Adam Barth.
+
+        Create a filesystem wrapper that we can use to enforce
+        particular conventions and use for mocking and dependency
+        injection down the line.
+
+        https://bugs.webkit.org/show_bug.cgi?id=48144
+
+        * Scripts/webkitpy/common/system/filesystem.py: Added.
+        * Scripts/webkitpy/common/system/filesystem_unittest.py: Added.
+
 2010-11-04  Mihai Parparita  <mihaip@chromium.org>
 
         Reviewed by Tony Chang.
diff --git a/WebKitTools/Scripts/webkitpy/common/system/filesystem.py b/WebKitTools/Scripts/webkitpy/common/system/filesystem.py
new file mode 100644 (file)
index 0000000..c7efde3
--- /dev/null
@@ -0,0 +1,117 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Wrapper object for the file system / source tree."""
+
+from __future__ import with_statement
+
+import codecs
+import errno
+import os
+import tempfile
+
+
+class FileSystem(object):
+    """FileSystem interface for webkitpy.
+
+    Unless otherwise noted, all paths are allowed to be either absolute
+    or relative."""
+
+    def exists(self, path):
+        """Return whether the path exists in the filesystem."""
+        return os.path.exists(path)
+
+    def isdir(self, path):
+        """Return whether the path refers to a directory."""
+        return os.path.isdir(path)
+
+    def join(self, *comps):
+        """Return the path formed by joining the components."""
+        return os.path.join(*comps)
+
+    def listdir(self, path):
+        """Return the contents of the directory pointed to by path."""
+        return os.listdir(path)
+
+    def mkdtemp(self, **kwargs):
+        """Create and return a uniquely named directory.
+
+        This is like tempfile.mkdtemp, but if used in a with statement
+        the directory will self-delete at the end of the block (if the
+        directory is empty; non-empty directories raise errors). The
+        directory can be safely deleted inside the block as well, if so
+        desired."""
+        class TemporaryDirectory(object):
+            def __init__(self, **kwargs):
+                self._kwargs = kwargs
+                self._directory_path = None
+
+            def __enter__(self):
+                self._directory_path = tempfile.mkdtemp(**self._kwargs)
+                return self._directory_path
+
+            def __exit__(self, type, value, traceback):
+                # Only self-delete if necessary.
+
+                # FIXME: Should we delete non-empty directories?
+                if os.path.exists(self._directory_path):
+                    os.rmdir(self._directory_path)
+
+        return TemporaryDirectory(**kwargs)
+
+    def maybe_make_directory(self, *path):
+        """Create the specified directory if it doesn't already exist."""
+        try:
+            os.makedirs(os.path.join(*path))
+        except OSError, e:
+            if e.errno != errno.EEXIST:
+                raise
+
+    def read_binary_file(self, path):
+        """Return the contents of the file at the given path as a byte string."""
+        with file(path, 'rb') as f:
+            return f.read()
+
+    def read_text_file(self, path):
+        """Return the contents of the file at the given path as a Unicode string.
+
+        The file is read assuming it is a UTF-8 encoded file with no BOM."""
+        with codecs.open(path, 'r', 'utf8') as f:
+            return f.read()
+
+    def write_binary_file(self, path, contents):
+        """Write the contents to the file at the given location."""
+        with file(path, 'wb') as f:
+            f.write(contents)
+
+    def write_text_file(self, path, contents):
+        """Write the contents to the file at the given location.
+
+        The file is written encoded as UTF-8 with no BOM."""
+        with codecs.open(path, 'w', 'utf8') as f:
+            f.write(contents)
diff --git a/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py b/WebKitTools/Scripts/webkitpy/common/system/filesystem_unittest.py
new file mode 100644 (file)
index 0000000..95684b7
--- /dev/null
@@ -0,0 +1,157 @@
+# vim: set fileencoding=utf-8 :
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#    * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#    * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#    * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# NOTE: The fileencoding comment on the first line of the file is
+# important; without it, Python will choke while trying to parse the file,
+# since it includes non-ASCII characters.
+
+from __future__ import with_statement
+
+import os
+import stat
+import sys
+import tempfile
+import unittest
+
+from filesystem import FileSystem
+
+
+class FileSystemTest(unittest.TestCase):
+    def setUp(self):
+        self._this_dir = os.path.dirname(os.path.abspath(__file__))
+        self._missing_file = os.path.join(self._this_dir, 'missing_file.py')
+        self._this_file = os.path.join(self._this_dir, 'filesystem_unittest.py')
+
+    def test_exists__true(self):
+        fs = FileSystem()
+        self.assertTrue(fs.exists(self._this_file))
+
+    def test_exists__false(self):
+        fs = FileSystem()
+        self.assertFalse(fs.exists(self._missing_file))
+
+    def test_isdir__true(self):
+        fs = FileSystem()
+        self.assertTrue(fs.isdir(self._this_dir))
+
+    def test_isdir__false(self):
+        fs = FileSystem()
+        self.assertFalse(fs.isdir(self._this_file))
+
+    def test_join(self):
+        fs = FileSystem()
+        self.assertEqual(fs.join('foo', 'bar'),
+                         os.path.join('foo', 'bar'))
+
+    def test_listdir(self):
+        fs = FileSystem()
+        with fs.mkdtemp(prefix='filesystem_unittest_') as d:
+            self.assertEqual(fs.listdir(d), [])
+            new_file = os.path.join(d, 'foo')
+            fs.write_text_file(new_file, u'foo')
+            self.assertEqual(fs.listdir(d), ['foo'])
+            os.remove(new_file)
+
+    def test_maybe_make_directory__success(self):
+        fs = FileSystem()
+
+        with fs.mkdtemp(prefix='filesystem_unittest_') as base_path:
+            sub_path = os.path.join(base_path, "newdir")
+            self.assertFalse(os.path.exists(sub_path))
+            self.assertFalse(fs.isdir(sub_path))
+
+            fs.maybe_make_directory(sub_path)
+            self.assertTrue(os.path.exists(sub_path))
+            self.assertTrue(fs.isdir(sub_path))
+
+            # Make sure we can re-create it.
+            fs.maybe_make_directory(sub_path)
+            self.assertTrue(os.path.exists(sub_path))
+            self.assertTrue(fs.isdir(sub_path))
+
+            # Clean up.
+            os.rmdir(sub_path)
+
+        self.assertFalse(os.path.exists(base_path))
+        self.assertFalse(fs.isdir(base_path))
+
+    def test_maybe_make_directory__failure(self):
+        # FIXME: os.chmod() doesn't work on Windows to set directories
+        # as readonly, so we skip this test for now.
+        if sys.platform in ('win32', 'cygwin'):
+            return
+
+        fs = FileSystem()
+        with fs.mkdtemp(prefix='filesystem_unittest_') as d:
+            # Remove write permissions on the parent directory.
+            os.chmod(d, stat.S_IRUSR)
+
+            # Now try to create a sub directory - should fail.
+            sub_dir = fs.join(d, 'subdir')
+            self.assertRaises(OSError, fs.maybe_make_directory, sub_dir)
+
+            # Clean up in case the test failed and we did create the
+            # directory.
+            if os.path.exists(sub_dir):
+                os.rmdir(sub_dir)
+
+    def test_read_and_write_file(self):
+        fs = FileSystem()
+        text_path = None
+        binary_path = None
+
+        unicode_text_string = u'Ūnĭcōde̽'
+        hex_equivalent = '\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD'
+        try:
+            text_path = tempfile.mktemp(prefix='tree_unittest_')
+            binary_path = tempfile.mktemp(prefix='tree_unittest_')
+            fs.write_text_file(text_path, unicode_text_string)
+            contents = fs.read_binary_file(text_path)
+            self.assertEqual(contents, hex_equivalent)
+
+            fs.write_text_file(binary_path, hex_equivalent)
+            text_contents = fs.read_text_file(binary_path)
+            self.assertEqual(text_contents, unicode_text_string)
+        except:
+            if text_path:
+                os.remove(text_path)
+            if binary_path:
+                os.remove(binary_path)
+
+    def test_read_binary_file__missing(self):
+        fs = FileSystem()
+        self.assertRaises(IOError, fs.read_binary_file, self._missing_file)
+
+    def test_read_text_file__missing(self):
+        fs = FileSystem()
+        self.assertRaises(IOError, fs.read_text_file, self._missing_file)
+
+
+if __name__ == '__main__':
+    unittest.main()