1# Copyright (C) 2010 Google Inc. All rights reserved.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are
5# met:
6#
7#    * Redistributions of source code must retain the above copyright
8# notice, this list of conditions and the following disclaimer.
9#    * Redistributions in binary form must reproduce the above
10# copyright notice, this list of conditions and the following disclaimer
11# in the documentation and/or other materials provided with the
12# distribution.
13#    * Neither the name of Google Inc. nor the names of its
14# contributors may be used to endorse or promote products derived from
15# this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29from __future__ import with_statement
30
31import codecs
32import os
33import shutil
34import tempfile
35import unittest
36
37from webkitpy.common.checkout.api import Checkout
38from webkitpy.common.checkout.changelog import ChangeLogEntry
39from webkitpy.common.checkout.scm import detect_scm_system, CommitMessage
40from webkitpy.common.system.outputcapture import OutputCapture
41from webkitpy.common.system.executive import ScriptError
42from webkitpy.thirdparty.mock import Mock
43
44
45# FIXME: Copied from scm_unittest.py
46def write_into_file_at_path(file_path, contents, encoding="utf-8"):
47    with codecs.open(file_path, "w", encoding) as file:
48        file.write(contents)
49
50
51_changelog1entry1 = u"""2010-03-25  Tor Arne Vestb\u00f8  <vestbo@webkit.org>
52
53        Unreviewed build fix to un-break webkit-patch land.
54
55        Move commit_message_for_this_commit from scm to checkout
56        https://bugs.webkit.org/show_bug.cgi?id=36629
57
58        * Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage
59"""
60_changelog1entry2 = u"""2010-03-25  Adam Barth  <abarth@webkit.org>
61
62        Reviewed by Eric Seidel.
63
64        Move commit_message_for_this_commit from scm to checkout
65        https://bugs.webkit.org/show_bug.cgi?id=36629
66
67        * Scripts/webkitpy/common/checkout/api.py:
68"""
69_changelog1 = u"\n".join([_changelog1entry1, _changelog1entry2])
70_changelog2 = u"""2010-03-25  Tor Arne Vestb\u00f8  <vestbo@webkit.org>
71
72        Unreviewed build fix to un-break webkit-patch land.
73
74        Second part of this complicated change.
75
76        * Path/To/Complicated/File: Added.
77
782010-03-25  Adam Barth  <abarth@webkit.org>
79
80        Reviewed by Eric Seidel.
81
82        Filler change.
83"""
84
85class CommitMessageForThisCommitTest(unittest.TestCase):
86    expected_commit_message = u"""2010-03-25  Tor Arne Vestb\u00f8  <vestbo@webkit.org>
87
88        Unreviewed build fix to un-break webkit-patch land.
89
90        Move commit_message_for_this_commit from scm to checkout
91        https://bugs.webkit.org/show_bug.cgi?id=36629
92
93        * Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage
942010-03-25  Tor Arne Vestb\u00f8  <vestbo@webkit.org>
95
96        Unreviewed build fix to un-break webkit-patch land.
97
98        Second part of this complicated change.
99
100        * Path/To/Complicated/File: Added.
101"""
102
103    def setUp(self):
104        self.temp_dir = tempfile.mkdtemp(suffix="changelogs")
105        self.old_cwd = os.getcwd()
106        os.chdir(self.temp_dir)
107        write_into_file_at_path("ChangeLog1", _changelog1)
108        write_into_file_at_path("ChangeLog2", _changelog2)
109
110    def tearDown(self):
111        shutil.rmtree(self.temp_dir, ignore_errors=True)
112        os.chdir(self.old_cwd)
113
114    # FIXME: This should not need to touch the file system, however
115    # ChangeLog is difficult to mock at current.
116    def test_commit_message_for_this_commit(self):
117        checkout = Checkout(None)
118        checkout.modified_changelogs = lambda git_commit, changed_files=None: ["ChangeLog1", "ChangeLog2"]
119        output = OutputCapture()
120        expected_stderr = "Parsing ChangeLog: ChangeLog1\nParsing ChangeLog: ChangeLog2\n"
121        commit_message = output.assert_outputs(self, checkout.commit_message_for_this_commit,
122            kwargs={"git_commit": None}, expected_stderr=expected_stderr)
123        self.assertEqual(commit_message.message(), self.expected_commit_message)
124
125
126class CheckoutTest(unittest.TestCase):
127    def test_latest_entry_for_changelog_at_revision(self):
128        scm = Mock()
129        def mock_contents_at_revision(changelog_path, revision):
130            self.assertEqual(changelog_path, "foo")
131            self.assertEqual(revision, "bar")
132            # contents_at_revision is expected to return a byte array (str)
133            # so we encode our unicode ChangeLog down to a utf-8 stream.
134            # The ChangeLog utf-8 decoding should ignore invalid codepoints.
135            invalid_utf8 = "\255"
136            return _changelog1.encode("utf-8") + invalid_utf8
137        scm.contents_at_revision = mock_contents_at_revision
138        checkout = Checkout(scm)
139        entry = checkout._latest_entry_for_changelog_at_revision("foo", "bar")
140        self.assertEqual(entry.contents(), _changelog1entry1)
141
142    # FIXME: This tests a hack around our current changed_files handling.
143    # Right now changelog_entries_for_revision tries to fetch deleted files
144    # from revisions, resulting in a ScriptError exception.  Test that we
145    # recover from those and still return the other ChangeLog entries.
146    def test_changelog_entries_for_revision(self):
147        scm = Mock()
148        scm.changed_files_for_revision = lambda revision: ['foo/ChangeLog', 'bar/ChangeLog']
149        checkout = Checkout(scm)
150
151        def mock_latest_entry_for_changelog_at_revision(path, revision):
152            if path == "foo/ChangeLog":
153                return 'foo'
154            raise ScriptError()
155
156        checkout._latest_entry_for_changelog_at_revision = mock_latest_entry_for_changelog_at_revision
157
158        # Even though fetching one of the entries failed, the other should succeed.
159        entries = checkout.changelog_entries_for_revision(1)
160        self.assertEqual(len(entries), 1)
161        self.assertEqual(entries[0], 'foo')
162
163    def test_commit_info_for_revision(self):
164        scm = Mock()
165        scm.committer_email_for_revision = lambda revision: "committer@example.com"
166        checkout = Checkout(scm)
167        checkout.changelog_entries_for_revision = lambda revision: [ChangeLogEntry(_changelog1entry1)]
168        commitinfo = checkout.commit_info_for_revision(4)
169        self.assertEqual(commitinfo.bug_id(), 36629)
170        self.assertEqual(commitinfo.author_name(), u"Tor Arne Vestb\u00f8")
171        self.assertEqual(commitinfo.author_email(), "vestbo@webkit.org")
172        self.assertEqual(commitinfo.reviewer_text(), None)
173        self.assertEqual(commitinfo.reviewer(), None)
174        self.assertEqual(commitinfo.committer_email(), "committer@example.com")
175        self.assertEqual(commitinfo.committer(), None)
176
177        checkout.changelog_entries_for_revision = lambda revision: []
178        self.assertEqual(checkout.commit_info_for_revision(1), None)
179
180    def test_bug_id_for_revision(self):
181        scm = Mock()
182        scm.committer_email_for_revision = lambda revision: "committer@example.com"
183        checkout = Checkout(scm)
184        checkout.changelog_entries_for_revision = lambda revision: [ChangeLogEntry(_changelog1entry1)]
185        self.assertEqual(checkout.bug_id_for_revision(4), 36629)
186
187    def test_bug_id_for_this_commit(self):
188        scm = Mock()
189        checkout = Checkout(scm)
190        checkout.commit_message_for_this_commit = lambda git_commit, changed_files=None: CommitMessage(ChangeLogEntry(_changelog1entry1).contents().splitlines())
191        self.assertEqual(checkout.bug_id_for_this_commit(git_commit=None), 36629)
192
193    def test_modified_changelogs(self):
194        scm = Mock()
195        scm.checkout_root = "/foo/bar"
196        scm.changed_files = lambda git_commit: ["file1", "ChangeLog", "relative/path/ChangeLog"]
197        checkout = Checkout(scm)
198        expected_changlogs = ["/foo/bar/ChangeLog", "/foo/bar/relative/path/ChangeLog"]
199        self.assertEqual(checkout.modified_changelogs(git_commit=None), expected_changlogs)
200
201    def test_suggested_reviewers(self):
202        def mock_changelog_entries_for_revision(revision):
203            if revision % 2 == 0:
204                return [ChangeLogEntry(_changelog1entry1)]
205            return [ChangeLogEntry(_changelog1entry2)]
206
207        def mock_revisions_changing_file(path, limit=5):
208            if path.endswith("ChangeLog"):
209                return [3]
210            return [4, 8]
211
212        scm = Mock()
213        scm.checkout_root = "/foo/bar"
214        scm.changed_files = lambda git_commit: ["file1", "file2", "relative/path/ChangeLog"]
215        scm.revisions_changing_file = mock_revisions_changing_file
216        checkout = Checkout(scm)
217        checkout.changelog_entries_for_revision = mock_changelog_entries_for_revision
218        reviewers = checkout.suggested_reviewers(git_commit=None)
219        reviewer_names = [reviewer.full_name for reviewer in reviewers]
220        self.assertEqual(reviewer_names, [u'Tor Arne Vestb\xf8'])
221