Refactor ChangeLog and PrepareChangeLog to use FileSystem objects
https://bugs.webkit.org/show_bug.cgi?id=107903

Patch by Timothy Loh <timloh@chromium.com> on 2013-01-28
Reviewed by Eric Seidel.

To make ChangeLog and PrepareChangeLog easier to test, we can use
FileSystem objects instead of just passing around paths.

* Scripts/webkitpy/common/checkout/changelog.py:
(ChangeLog.__init__):
(ChangeLog.parse_latest_entry_from_file):
(ChangeLog.parse_entries_from_file):
(ChangeLog.latest_entry):
(ChangeLog.update_with_unreviewed_message):
(ChangeLog.set_reviewer):
(ChangeLog.set_short_description_and_bug_url):
(ChangeLog.delete_entries):
(ChangeLog.prepend_text):
* Scripts/webkitpy/common/checkout/changelog_unittest.py:
(ChangeLogTest):
(test_parse_log_entries_from_changelog):
(test_latest_entry_parse_single_entry):
(test_set_reviewer):
(test_set_short_description_and_bug_url):
(test_delete_entries):
(test_prepend_text):
* Scripts/webkitpy/common/system/filesystem_mock.py:
(ReadableTextFileObject.__init__):
* Scripts/webkitpy/tool/steps/preparechangelog.py:
(PrepareChangeLog._ensure_bug_url):
(PrepareChangeLog._resolve_existing_entry):
(PrepareChangeLog.run):
* Scripts/webkitpy/tool/steps/preparechangelog_unittest.py:
(test_ensure_bug_url):
* Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py:
(_assert_message_for_revert_output):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@141022 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 5762a36..26d4c06 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,42 @@
+2013-01-28  Timothy Loh  <timloh@chromium.com>
+
+        Refactor ChangeLog and PrepareChangeLog to use FileSystem objects
+        https://bugs.webkit.org/show_bug.cgi?id=107903
+
+        Reviewed by Eric Seidel.
+
+        To make ChangeLog and PrepareChangeLog easier to test, we can use
+        FileSystem objects instead of just passing around paths.
+
+        * Scripts/webkitpy/common/checkout/changelog.py:
+        (ChangeLog.__init__):
+        (ChangeLog.parse_latest_entry_from_file):
+        (ChangeLog.parse_entries_from_file):
+        (ChangeLog.latest_entry):
+        (ChangeLog.update_with_unreviewed_message):
+        (ChangeLog.set_reviewer):
+        (ChangeLog.set_short_description_and_bug_url):
+        (ChangeLog.delete_entries):
+        (ChangeLog.prepend_text):
+        * Scripts/webkitpy/common/checkout/changelog_unittest.py:
+        (ChangeLogTest):
+        (test_parse_log_entries_from_changelog):
+        (test_latest_entry_parse_single_entry):
+        (test_set_reviewer):
+        (test_set_short_description_and_bug_url):
+        (test_delete_entries):
+        (test_prepend_text):
+        * Scripts/webkitpy/common/system/filesystem_mock.py:
+        (ReadableTextFileObject.__init__):
+        * Scripts/webkitpy/tool/steps/preparechangelog.py:
+        (PrepareChangeLog._ensure_bug_url):
+        (PrepareChangeLog._resolve_existing_entry):
+        (PrepareChangeLog.run):
+        * Scripts/webkitpy/tool/steps/preparechangelog_unittest.py:
+        (test_ensure_bug_url):
+        * Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py:
+        (_assert_message_for_revert_output):
+
 2013-01-28  Raymond Toy  <rtoy@google.com>
 
         Add myself to committers.py
diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog.py b/Tools/Scripts/webkitpy/common/checkout/changelog.py
index 748182f..80b2335 100644
--- a/Tools/Scripts/webkitpy/common/checkout/changelog.py
+++ b/Tools/Scripts/webkitpy/common/checkout/changelog.py
@@ -28,14 +28,14 @@
 #
 # WebKit's Python module for parsing and modifying ChangeLog files
 
-import codecs
-import fileinput # inplace file editing for set_reviewer_in_changelog
 import logging
 import re
+from StringIO import StringIO
 import textwrap
 
 from webkitpy.common.config.committers import CommitterList
 from webkitpy.common.config.committers import Account
+from webkitpy.common.system.filesystem import FileSystem
 import webkitpy.common.config.urls as config_urls
 
 _log = logging.getLogger(__name__)
@@ -282,33 +282,18 @@
 # FIXME: Various methods on ChangeLog should move into ChangeLogEntry instead.
 class ChangeLog(object):
 
-    def __init__(self, path):
+    def __init__(self, path, filesystem=None):
         self.path = path
+        self._filesystem = filesystem or FileSystem()
 
     _changelog_indent = " " * 8
 
     @classmethod
     def parse_latest_entry_from_file(cls, changelog_file):
-        """changelog_file must be a file-like object which returns
-        unicode strings.  Use codecs.open or StringIO(unicode())
-        to pass file objects to this class."""
-        date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp)
-        rolled_over_regexp = re.compile(ChangeLogEntry.rolled_over_regexp)
-        entry_lines = []
-        # The first line should be a date line.
-        first_line = changelog_file.readline()
-        assert(isinstance(first_line, unicode))
-        if not date_line_regexp.match(first_line):
+        try:
+            return next(cls.parse_entries_from_file(changelog_file))
+        except StopIteration, e:
             return None
-        entry_lines.append(first_line)
-
-        for line in changelog_file:
-            # If we've hit the next entry, return.
-            if date_line_regexp.match(line) or rolled_over_regexp.match(line):
-                # Remove the extra newline at the end
-                return ChangeLogEntry(''.join(entry_lines[:-1]))
-            entry_lines.append(line)
-        return None # We never found a date line!
 
     svn_blame_regexp = re.compile(r'^(\s*(?P<revision>\d+) [^ ]+)\s*(?P<line>.*?\n)')
 
@@ -322,8 +307,8 @@
     @classmethod
     def parse_entries_from_file(cls, changelog_file):
         """changelog_file must be a file-like object which returns
-        unicode strings.  Use codecs.open or StringIO(unicode())
-        to pass file objects to this class."""
+        unicode strings, e.g. from StringIO(unicode()) or
+        fs.open_text_file_for_reading()"""
         date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp)
         rolled_over_regexp = re.compile(ChangeLogEntry.rolled_over_regexp)
 
@@ -358,7 +343,7 @@
 
     def latest_entry(self):
         # ChangeLog files are always UTF-8, we read them in as such to support Reviewers with unicode in their names.
-        changelog_file = codecs.open(self.path, "r", "utf-8")
+        changelog_file = self._filesystem.open_text_file_for_reading(self.path)
         try:
             return self.parse_latest_entry_from_file(changelog_file)
         finally:
@@ -386,20 +371,22 @@
         first_boilerplate_line_regexp = re.compile(
                 "%sNeed a short description \(OOPS!\)\." % self._changelog_indent)
         removing_boilerplate = False
-        # inplace=1 creates a backup file and re-directs stdout to the file
-        for line in fileinput.FileInput(self.path, inplace=1):
-            if first_boilerplate_line_regexp.search(line):
-                message_lines = self._wrap_lines(message)
-                print first_boilerplate_line_regexp.sub(message_lines, line),
-                # Remove all the ChangeLog boilerplate before the first changed
-                # file.
-                removing_boilerplate = True
-            elif removing_boilerplate:
-                if line.find('*') >= 0: # each changed file is preceded by a *
-                    removing_boilerplate = False
+        result = StringIO()
+        with self._filesystem.open_text_file_for_reading(self.path) as file:
+            for line in file:
+                if first_boilerplate_line_regexp.search(line):
+                    message_lines = self._wrap_lines(message)
+                    result.write(first_boilerplate_line_regexp.sub(message_lines, line))
+                    # Remove all the ChangeLog boilerplate before the first changed
+                    # file.
+                    removing_boilerplate = True
+                elif removing_boilerplate:
+                    if line.find('*') >= 0:  # each changed file is preceded by a *
+                        removing_boilerplate = False
 
-            if not removing_boilerplate:
-                print line,
+                if not removing_boilerplate:
+                    result.write(line)
+        self._filesystem.write_text_file(self.path, result.getvalue())
 
     def set_reviewer(self, reviewer):
         latest_entry = self.latest_entry()
@@ -410,41 +397,49 @@
         if not found_nobody and not reviewer_text:
             bug_url_number_of_items = len(re.findall(config_urls.bug_url_long, latest_entry_contents, re.MULTILINE))
             bug_url_number_of_items += len(re.findall(config_urls.bug_url_short, latest_entry_contents, re.MULTILINE))
-            for line in fileinput.FileInput(self.path, inplace=1):
-                found_bug_url = re.search(config_urls.bug_url_long, line)
-                if not found_bug_url:
-                    found_bug_url = re.search(config_urls.bug_url_short, line)
-                print line,
-                if found_bug_url:
-                    if bug_url_number_of_items == 1:
-                        print "\n        Reviewed by %s." % (reviewer.encode("utf-8"))
-                    bug_url_number_of_items -= 1
+            result = StringIO()
+            with self._filesystem.open_text_file_for_reading(self.path) as file:
+                for line in file:
+                    found_bug_url = re.search(config_urls.bug_url_long, line)
+                    if not found_bug_url:
+                        found_bug_url = re.search(config_urls.bug_url_short, line)
+                    result.write(line)
+                    if found_bug_url:
+                        if bug_url_number_of_items == 1:
+                            result.write("\n        Reviewed by %s.\n" % reviewer)
+                        bug_url_number_of_items -= 1
+            self._filesystem.write_text_file(self.path, result.getvalue())
         else:
-            # inplace=1 creates a backup file and re-directs stdout to the file
-            for line in fileinput.FileInput(self.path, inplace=1):
-                # Trailing comma suppresses printing newline
-                print line.replace("NOBODY (OOPS!)", reviewer.encode("utf-8")),
+            data = self._filesystem.read_text_file(self.path)
+            newdata = data.replace("NOBODY (OOPS!)", reviewer)
+            self._filesystem.write_text_file(self.path, newdata)
 
     def set_short_description_and_bug_url(self, short_description, bug_url):
         message = "%s\n%s%s" % (short_description, self._changelog_indent, bug_url)
         bug_boilerplate = "%sNeed the bug URL (OOPS!).\n" % self._changelog_indent
-        for line in fileinput.FileInput(self.path, inplace=1):
-            line = line.replace("Need a short description (OOPS!).", message.encode("utf-8"))
-            if line != bug_boilerplate:
-                print line,
+        result = StringIO()
+        with self._filesystem.open_text_file_for_reading(self.path) as file:
+            for line in file:
+                line = line.replace("Need a short description (OOPS!).", message)
+                if line != bug_boilerplate:
+                    result.write(line)
+        self._filesystem.write_text_file(self.path, result.getvalue())
 
     def delete_entries(self, num_entries):
         date_line_regexp = re.compile(ChangeLogEntry.date_line_regexp)
         rolled_over_regexp = re.compile(ChangeLogEntry.rolled_over_regexp)
         entries = 0
-        for line in fileinput.FileInput(self.path, inplace=1):
-            if date_line_regexp.match(line):
-                entries += 1
-            elif rolled_over_regexp.match(line):
-                entries = num_entries + 1
-            if entries > num_entries:
-                print line,
+        result = StringIO()
+        with self._filesystem.open_text_file_for_reading(self.path) as file:
+            for line in file:
+                if date_line_regexp.match(line):
+                    entries += 1
+                elif rolled_over_regexp.match(line):
+                    entries = num_entries + 1
+                if entries > num_entries:
+                    result.write(line)
+        self._filesystem.write_text_file(self.path, result.getvalue())
 
     def prepend_text(self, text):
-        data = codecs.open(self.path, "r", "utf-8").read()
-        codecs.open(self.path, "w", "utf-8").write(text + data)
+        data = self._filesystem.read_text_file(self.path)
+        self._filesystem.write_text_file(self.path, text + data)
diff --git a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py b/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py
index ec414bc..55b8b86 100644
--- a/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py
+++ b/Tools/Scripts/webkitpy/common/checkout/changelog_unittest.py
@@ -26,18 +26,18 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import codecs
-import os
-import tempfile
 import unittest2 as unittest
 
 from StringIO import StringIO
 
+from webkitpy.common.system.filesystem_mock import MockFileSystem
 from webkitpy.common.checkout.changelog import *
 
 
 class ChangeLogTest(unittest.TestCase):
 
+    _changelog_path = 'Tools/ChangeLog'
+
     _example_entry = u'''2009-08-17  Peter Kasting  <pkasting@google.com>
 
         Reviewed by Tor Arne Vestb\xf8.
@@ -254,7 +254,7 @@
             "WebCoreSupport/ChromeClientEfl.cpp": ["WebCore::ChromeClientEfl::closeWindowSoon"], "ewk/ewk_private.h": [], "ewk/ewk_view.cpp": []})
         self.assertEqual(parsed_entries[3].bug_description(), "[Mac] ResourceRequest's nsURLRequest() does not differentiate null and empty URLs with CFNetwork")
         self.assertEqual(parsed_entries[4].reviewer_text(), "David Hyatt")
-        self.assertEqual(parsed_entries[4].bug_description(), None)
+        self.assertIsNone(parsed_entries[4].bug_description())
         self.assertEqual(parsed_entries[5].reviewer_text(), "Adam Roben")
         self.assertEqual(parsed_entries[6].reviewer_text(), "Tony Chang")
         self.assertIsNone(parsed_entries[7].reviewer_text())
@@ -486,19 +486,6 @@
         self.assertEqual(latest_entry.contents(), self._example_entry)
         self.assertEqual(latest_entry.author_name(), "Peter Kasting")
 
-    @staticmethod
-    def _write_tmp_file_with_contents(byte_array):
-        assert(isinstance(byte_array, str))
-        (file_descriptor, file_path) = tempfile.mkstemp() # NamedTemporaryFile always deletes the file on close in python < 2.6
-        with os.fdopen(file_descriptor, "w") as file:
-            file.write(byte_array)
-        return file_path
-
-    @staticmethod
-    def _read_file_contents(file_path, encoding):
-        with codecs.open(file_path, "r", encoding) as file:
-            return file.read()
-
     # FIXME: We really should be getting this from prepare-ChangeLog itself.
     _new_entry_boilerplate = '''2009-08-19  Eric Seidel  <eric@webkit.org>
 
@@ -549,58 +536,58 @@
 '''
 
     def test_set_reviewer(self):
+        fs = MockFileSystem()
+
         changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate_with_bugurl, self._example_changelog)
-        changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
         reviewer_name = 'Test Reviewer'
-        ChangeLog(changelog_path).set_reviewer(reviewer_name)
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        fs.write_text_file(self._changelog_path, changelog_contents)
+        ChangeLog(self._changelog_path, fs).set_reviewer(reviewer_name)
+        actual_contents = fs.read_text_file(self._changelog_path)
         expected_contents = changelog_contents.replace('NOBODY (OOPS!)', reviewer_name)
-        os.remove(changelog_path)
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
 
         changelog_contents_without_reviewer_line = u"%s\n%s" % (self._new_entry_boilerplate_without_reviewer_line, self._example_changelog)
-        changelog_path = self._write_tmp_file_with_contents(changelog_contents_without_reviewer_line.encode("utf-8"))
-        ChangeLog(changelog_path).set_reviewer(reviewer_name)
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
-        os.remove(changelog_path)
+        fs.write_text_file(self._changelog_path, changelog_contents_without_reviewer_line)
+        ChangeLog(self._changelog_path, fs).set_reviewer(reviewer_name)
+        actual_contents = fs.read_text_file(self._changelog_path)
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
 
         changelog_contents_without_reviewer_line = u"%s\n%s" % (self._new_entry_boilerplate_without_reviewer_multiple_bugurl, self._example_changelog)
-        changelog_path = self._write_tmp_file_with_contents(changelog_contents_without_reviewer_line.encode("utf-8"))
-        ChangeLog(changelog_path).set_reviewer(reviewer_name)
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        fs.write_text_file(self._changelog_path, changelog_contents_without_reviewer_line)
+        ChangeLog(self._changelog_path, fs).set_reviewer(reviewer_name)
+        actual_contents = fs.read_text_file(self._changelog_path)
         changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate_with_multiple_bugurl, self._example_changelog)
         expected_contents = changelog_contents.replace('NOBODY (OOPS!)', reviewer_name)
-        os.remove(changelog_path)
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
 
     def test_set_short_description_and_bug_url(self):
+        fs = MockFileSystem()
+
         changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate_with_bugurl, self._example_changelog)
-        changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
+        fs.write_text_file(self._changelog_path, changelog_contents)
         short_description = "A short description"
         bug_url = "http://example.com/b/2344"
-        ChangeLog(changelog_path).set_short_description_and_bug_url(short_description, bug_url)
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        ChangeLog(self._changelog_path, fs).set_short_description_and_bug_url(short_description, bug_url)
+        actual_contents = fs.read_text_file(self._changelog_path)
         expected_message = "%s\n        %s" % (short_description, bug_url)
         expected_contents = changelog_contents.replace("Need a short description (OOPS!).", expected_message)
-        os.remove(changelog_path)
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
 
         changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog)
-        changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
+        fs.write_text_file(self._changelog_path, changelog_contents)
         short_description = "A short description 2"
         bug_url = "http://example.com/b/2345"
-        ChangeLog(changelog_path).set_short_description_and_bug_url(short_description, bug_url)
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        ChangeLog(self._changelog_path, fs).set_short_description_and_bug_url(short_description, bug_url)
+        actual_contents = fs.read_text_file(self._changelog_path)
         expected_message = "%s\n        %s" % (short_description, bug_url)
         expected_contents = changelog_contents.replace("Need a short description (OOPS!).\n        Need the bug URL (OOPS!).", expected_message)
-        os.remove(changelog_path)
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
 
     def test_delete_entries(self):
-        changelog_path = self._write_tmp_file_with_contents(self._example_changelog.encode("utf-8"))
-        ChangeLog(changelog_path).delete_entries(8)
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        fs = MockFileSystem()
+        fs.write_text_file(self._changelog_path, self._example_changelog)
+        ChangeLog(self._changelog_path, fs).delete_entries(8)
+        actual_contents = fs.read_text_file(self._changelog_path)
         expected_contents = """2011-10-11  Antti Koivisto  <antti@apple.com>
 
        Resolve regular and visited link style in a single pass
@@ -616,17 +603,16 @@
 """
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
 
-        ChangeLog(changelog_path).delete_entries(2)
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        ChangeLog(self._changelog_path, fs).delete_entries(2)
+        actual_contents = fs.read_text_file(self._changelog_path)
         expected_contents = "== Rolled over to ChangeLog-2009-06-16 ==\n"
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
 
-        os.remove(changelog_path)
 
     def test_prepend_text(self):
-        changelog_path = self._write_tmp_file_with_contents(self._example_changelog.encode("utf-8"))
-        ChangeLog(changelog_path).prepend_text(self._example_entry + "\n")
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        fs = MockFileSystem()
+        fs.write_text_file(self._changelog_path, self._example_changelog)
+        ChangeLog(self._changelog_path, fs).prepend_text(self._example_entry + "\n")
+        actual_contents = fs.read_text_file(self._changelog_path)
         expected_contents = self._example_entry + "\n" + self._example_changelog
         self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
-        os.remove(changelog_path)
diff --git a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py
index 16e9fad..4c6bd97 100644
--- a/Tools/Scripts/webkitpy/common/system/filesystem_mock.py
+++ b/Tools/Scripts/webkitpy/common/system/filesystem_mock.py
@@ -452,7 +452,7 @@
 
 class ReadableTextFileObject(ReadableBinaryFileObject):
     def __init__(self, fs, path, data):
-        super(ReadableTextFileObject, self).__init__(fs, path, StringIO.StringIO(data))
+        super(ReadableTextFileObject, self).__init__(fs, path, StringIO.StringIO(data.decode("utf-8")))
 
     def close(self):
         self.data.close()
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py
index 36d2eb2..5a052ee 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py
+++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelog.py
@@ -26,9 +26,7 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import codecs
 import logging
-import os
 import re
 import sys
 
@@ -55,7 +53,7 @@
         bug_id = state.get("bug_id")
         changelogs = self.cached_lookup(state, "changelogs")
         for changelog_path in changelogs:
-            changelog = ChangeLog(changelog_path)
+            changelog = ChangeLog(changelog_path, self._tool.filesystem)
             if not changelog.latest_entry().bug_id():
                 changelog.set_short_description_and_bug_url(
                     self.cached_lookup(state, "bug_title"),
@@ -64,7 +62,7 @@
     def _resolve_existing_entry(self, changelog_path):
         # When this is called, the top entry in the ChangeLog was just created
         # by prepare-ChangeLog, as an clean updated version of the one below it.
-        with codecs.open(changelog_path, "r", "utf-8") as changelog_file:
+        with self._tool.filesystem.open_text_file_for_reading(changelog_path) as changelog_file:
             entries_gen = ChangeLog.parse_entries_from_file(changelog_file)
             entries = zip(entries_gen, range(2))
 
@@ -78,7 +76,7 @@
         (new_entry, _), (old_entry, _) = entries
         final_entry = self._merge_entries(old_entry, new_entry)
 
-        changelog = ChangeLog(changelog_path)
+        changelog = ChangeLog(changelog_path, self._tool.filesystem)
         changelog.delete_entries(2)
         changelog.prepend_text(final_entry)
 
@@ -129,7 +127,7 @@
 
         # These are the ChangeLog entries added by prepare-Changelog
         changelogs = re.findall(r'Editing the (\S*/ChangeLog) file.', output)
-        changelogs = set(os.path.join(self._tool.scm().checkout_root, f) for f in changelogs)
+        changelogs = set(self._tool.filesystem.join(self._tool.scm().checkout_root, f) for f in changelogs)
         for changelog in changelogs & set(self.cached_lookup(state, "changelogs")):
             self._resolve_existing_entry(changelog)
 
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py
index 6443049..a703ca2 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelog_unittest.py
@@ -26,19 +26,16 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import os
 import unittest2 as unittest
 
 # Do not import changelog_unittest.ChangeLogTest directly as that will cause it to be run again.
 from webkitpy.common.checkout import changelog_unittest
 
+from webkitpy.common.system.filesystem_mock import MockFileSystem
 from webkitpy.common.system.outputcapture import OutputCapture
 from webkitpy.tool.mocktool import MockOptions, MockTool
 from webkitpy.tool.steps.preparechangelog import PrepareChangeLog
 
-# FIXME: These tests should use a MockFileSystem instead of a real file system,
-# once changelog.py and preparechangelog.py are FileSystem compatible.
-
 class PrepareChangeLogTest(changelog_unittest.ChangeLogTest):
     def test_resolve_existing_entry(self):
         step = PrepareChangeLog(MockTool(), MockOptions())
@@ -106,24 +103,27 @@
             final_entry = make_entry(final)
             end_file = final_entry + roll_over
 
-            path = self._write_tmp_file_with_contents(start_file.encode("utf-8"))
+            path = "ChangeLog"
+            step._tool.filesystem = MockFileSystem()
+            step._tool.filesystem.write_text_file(path, start_file)
             step._resolve_existing_entry(path)
-            actual_output = self._read_file_contents(path, "utf-8")
+            actual_output = step._tool.filesystem.read_text_file(path)
             self.assertEquals(actual_output, end_file)
 
     def test_ensure_bug_url(self):
         capture = OutputCapture()
         step = PrepareChangeLog(MockTool(), MockOptions())
         changelog_contents = u"%s\n%s" % (self._new_entry_boilerplate, self._example_changelog)
-        changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
+        changelog_path = "ChangeLog"
         state = {
             "bug_title": "Example title",
             "bug_id": 1234,
             "changelogs": [changelog_path],
         }
+        step._tool.filesystem = MockFileSystem()
+        step._tool.filesystem.write_text_file(changelog_path, changelog_contents)
         capture.assert_outputs(self, step._ensure_bug_url, [state])
-        actual_contents = self._read_file_contents(changelog_path, "utf-8")
+        actual_contents = step._tool.filesystem.read_text_file(changelog_path)
         expected_message = "Example title\n        http://example.com/1234"
         expected_contents = changelog_contents.replace("Need a short description (OOPS!).\n        Need the bug URL (OOPS!).", expected_message)
-        os.remove(changelog_path)
-        self.assertEqual(actual_contents.splitlines(), expected_contents.splitlines())
+        self.assertEqual(actual_contents, expected_contents)
diff --git a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py
index a017988..3ec6e9a 100644
--- a/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py
+++ b/Tools/Scripts/webkitpy/tool/steps/preparechangelogforrevert_unittest.py
@@ -26,27 +26,17 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import codecs
-import os
-import tempfile
 import unittest2 as unittest
 
 # Do not import changelog_unittest.ChangeLogTest directly as that will cause it to be run again.
 from webkitpy.common.checkout import changelog_unittest
 
 from webkitpy.common.checkout.changelog import ChangeLog
+from webkitpy.common.system.filesystem_mock import MockFileSystem
 from webkitpy.tool.steps.preparechangelogforrevert import *
 
 
 class UpdateChangeLogsForRevertTest(unittest.TestCase):
-    @staticmethod
-    def _write_tmp_file_with_contents(byte_array):
-        assert(isinstance(byte_array, str))
-        (file_descriptor, file_path) = tempfile.mkstemp()  # NamedTemporaryFile always deletes the file on close in python < 2.6
-        with os.fdopen(file_descriptor, "w") as file:
-            file.write(byte_array)
-        return file_path
-
     _revert_entry_with_bug_url = '''2009-08-19  Eric Seidel  <eric@webkit.org>
 
         Unreviewed, rolling out r12345.
@@ -110,11 +100,11 @@
 
     def _assert_message_for_revert_output(self, args, expected_entry):
         changelog_contents = u"%s\n%s" % (changelog_unittest.ChangeLogTest._new_entry_boilerplate, changelog_unittest.ChangeLogTest._example_changelog)
-        changelog_path = self._write_tmp_file_with_contents(changelog_contents.encode("utf-8"))
-        changelog = ChangeLog(changelog_path)
+        changelog_path = "ChangeLog"
+        fs = MockFileSystem({changelog_path: changelog_contents.encode("utf-8")})
+        changelog = ChangeLog(changelog_path, fs)
         changelog.update_with_unreviewed_message(PrepareChangeLogForRevert._message_for_revert(*args))
         actual_entry = changelog.latest_entry()
-        os.remove(changelog_path)
         self.assertMultiLineEqual(actual_entry.contents(), expected_entry)
         self.assertIsNone(actual_entry.reviewer_text())
         # These checks could be removed to allow this to work on other entries: