RegistrationDatabase::openSQLiteDatabase can spin
[WebKit-https.git] / Tools / Scripts / webkitpy / common / checkout / checkout_unittest.py
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
29 import codecs
30 import os
31 import shutil
32 import tempfile
33 import unittest
34
35 from webkitpy.common.checkout.checkout import Checkout
36 from webkitpy.common.checkout.changelog import ChangeLogEntry
37 from webkitpy.common.checkout.scm import CommitMessage, SCMDetector
38 from webkitpy.common.checkout.scm.scm_mock import MockSCM
39 from webkitpy.common.webkit_finder import WebKitFinder
40 from webkitpy.common.system.executive import Executive, ScriptError
41 from webkitpy.common.system.filesystem import FileSystem  # FIXME: This should not be needed.
42 from webkitpy.common.system.filesystem_mock import MockFileSystem
43 from webkitpy.common.system.executive_mock import MockExecutive
44 from webkitpy.common.system.outputcapture import OutputCapture
45 from webkitpy.common.unicode_compatibility import encode_if_necessary
46 from webkitpy.thirdparty.mock import Mock
47
48
49 _changelog1entry1 = u"""2010-03-25  Fr\u00e9d\u00e9ric Wang  <fred.wang@free.fr>
50
51         Unreviewed build fix to un-break webkit-patch land.
52
53         Move commit_message_for_this_commit from scm to checkout
54         https://bugs.webkit.org/show_bug.cgi?id=36629
55
56         * Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage
57 """
58 _changelog1entry2 = u"""2010-03-25  Adam Barth  <abarth@webkit.org>
59
60         Reviewed by Eric Seidel.
61
62         Move commit_message_for_this_commit from scm to checkout
63         https://bugs.webkit.org/show_bug.cgi?id=36629
64
65         * Scripts/webkitpy/common/checkout/api.py:
66 """
67 _changelog1 = u"\n".join([_changelog1entry1, _changelog1entry2])
68 _changelog2 = u"""2010-03-25  Fr\u00e9d\u00e9ric Wang  <fred.wang@free.fr>
69
70         Unreviewed build fix to un-break webkit-patch land.
71
72         Second part of this complicated change by me, Fr\u00e9d\u00e9ric Wang!
73
74         * Path/To/Complicated/File: Added.
75
76 2010-03-25  Adam Barth  <abarth@webkit.org>
77
78         Reviewed by Eric Seidel.
79
80         Filler change.
81 """
82
83 _changelog3 = u"""2014-07-17  David Kilzer  <ddkilzer@apple.com>
84
85         SECTORDER_FLAGS should be defined in target's xcconfig file, not Base.xcconfig
86         <http://webkit.org/b/135006>
87
88         Reviewed by Darin Adler.
89
90         * WebKit.xcodeproj/project.pbxproj: Remove references to unused
91         WebKit.xcconfig file.
92 """
93
94 _changelog4 = u"""2014-07-17  David Kilzer  <ddkilzer@apple.com>
95
96         SECTORDER_FLAGS should be defined in target's xcconfig file, not Base.xcconfig
97         <http://webkit.org/b/135006>
98
99         Reviewed by Darin Adler.
100
101         * Path/To/Complicated/File: Added.
102 """
103
104 _changelog5 = u"""2014-07-17  David Kilzer  <ddkilzer@apple.com>
105
106         SECTORDER_FLAGS should be defined in target's xcconfig file, not Base.xcconfig
107         <http://webkit.org/b/135006>
108
109         Reviewed by Darin Adler.
110
111         Filler change.
112
113         * Path/To/Complicated/File: Added.
114 """
115
116 _changelog6 = u"""2014-06-23  Daniel Bates  <dabates@apple.com>
117
118         Reverting r170340 and r170339.
119
120         Changeset r170339 broke the Apple Windows Debug and Release builds.
121
122         Reverted changesets:
123
124         "[Win] Build fix after r134209"
125         https://trac.webkit.org/changeset/170340
126
127         "[Win] Clean up and refactor WinLauncher"
128         https://bugs.webkit.org/show_bug.cgi?id=134209
129         https://trac.webkit.org/changeset/170339
130 """
131
132
133 class CommitMessageForThisCommitTest(unittest.TestCase):
134     def setUp(self):
135         # FIXME: This should not need to touch the filesystem, however
136         # ChangeLog is difficult to mock at current.
137         self.filesystem = FileSystem()
138         self.temp_dir = str(self.filesystem.mkdtemp(suffix="changelogs"))
139         self.old_cwd = self.filesystem.getcwd()
140         self.filesystem.chdir(self.temp_dir)
141         self.webkit_base = WebKitFinder(self.filesystem).webkit_base()
142
143         # Trick commit-log-editor into thinking we're in a Subversion working copy so it won't
144         # complain about not being able to figure out what SCM is in use.
145         # FIXME: VCSTools.pm is no longer so easily fooled.  It logs because "svn info" doesn't
146         # treat a bare .svn directory being part of an svn checkout.
147         self.filesystem.maybe_make_directory(".svn")
148
149     def mock_changelog(self, changelogs):
150         for path, contents in zip(self.changelog_paths, changelogs):
151             self.filesystem.maybe_make_directory(self.filesystem.dirname(path))
152             self.filesystem.write_text_file(path, contents)
153
154     def tearDown(self):
155         self.filesystem.rmtree(self.temp_dir)
156         self.filesystem.chdir(self.old_cwd)
157
158     def mock_checkout_for_test(self):
159         executive = Executive()
160
161         def mock_run(*args, **kwargs):
162             # Note that we use a real Executive here, not a MockExecutive, so we can test that we're
163             # invoking commit-log-editor correctly.
164             env = os.environ.copy()
165             env['CHANGE_LOG_EMAIL_ADDRESS'] = 'fred.wang@free.fr'
166             kwargs['env'] = env
167             return executive.run_command(*args, **kwargs)
168
169         detector = SCMDetector(self.filesystem, executive)
170         real_scm = detector.detect_scm_system(self.webkit_base)
171
172         mock_scm = MockSCM()
173         mock_scm.run = mock_run
174
175         real_checkout = Checkout(real_scm)
176         checkout = Checkout(mock_scm)
177         checkout.script_path = real_checkout.script_path
178         checkout.modified_changelogs = lambda git_commit, changed_files=None: self.changelog_paths
179
180         return checkout
181
182     def test_commit_message_for_unreviewed_changelogs_with_different_messages(self):
183         expected_commit_message = u"""Unreviewed build fix to un-break webkit-patch land.
184
185 Tools:
186
187 Move commit_message_for_this_commit from scm to checkout
188 https://bugs.webkit.org/show_bug.cgi?id=36629
189
190 * Scripts/webkitpy/common/checkout/api.py: import scm.CommitMessage
191
192 LayoutTests:
193
194 Second part of this complicated change by me, Fr\u00e9d\u00e9ric Wang!
195
196 * Path/To/Complicated/File: Added.
197 """
198
199         self.changelog_paths = list(map(self.filesystem.abspath, (self.filesystem.join("Tools", "ChangeLog"), self.filesystem.join("LayoutTests", "ChangeLog"))))
200
201         self.mock_changelog((_changelog1, _changelog2))
202         checkout = self.mock_checkout_for_test()
203         commit_message = checkout.commit_message_for_this_commit(git_commit=None, return_stderr=True)
204         # Throw away the first line - a warning about unknown VCS root.
205         commit_message.message_lines = commit_message.message_lines[1:]
206         self.assertMultiLineEqual(commit_message.message(), expected_commit_message)
207
208     def test_commit_message_for_one_reviewed_changelog(self):
209         expected_commit_message = u"""SECTORDER_FLAGS should be defined in target's xcconfig file, not Base.xcconfig
210 <http://webkit.org/b/135006>
211
212 Patch by David Kilzer <ddkilzer@apple.com> on 2014-07-17
213 Reviewed by Darin Adler.
214
215 * WebKit.xcodeproj/project.pbxproj: Remove references to unused
216 WebKit.xcconfig file.
217 """
218
219         self.changelog_paths = list(map(self.filesystem.abspath, [self.filesystem.join("Source/WebKitLegacy", "ChangeLog")]))
220
221         self.mock_changelog([_changelog3])
222         checkout = self.mock_checkout_for_test()
223         commit_message = checkout.commit_message_for_this_commit(git_commit=None, return_stderr=True)
224         # Throw away the first line - a warning about unknown VCS root.
225         commit_message.message_lines = commit_message.message_lines[1:]
226         self.assertMultiLineEqual(commit_message.message(), expected_commit_message)
227
228     def test_commit_message_for_changelogs_with_same_messages(self):
229         expected_commit_message = u"""SECTORDER_FLAGS should be defined in target's xcconfig file, not Base.xcconfig
230 <http://webkit.org/b/135006>
231
232 Patch by David Kilzer <ddkilzer@apple.com> on 2014-07-17
233 Reviewed by Darin Adler.
234
235 Source/WebKitLegacy:
236
237 * WebKit.xcodeproj/project.pbxproj: Remove references to unused
238 WebKit.xcconfig file.
239
240 LayoutTests:
241
242 * Path/To/Complicated/File: Added.
243 """
244
245         self.changelog_paths = list(map(self.filesystem.abspath, (self.filesystem.join("Source/WebKitLegacy", "ChangeLog"), self.filesystem.join("LayoutTests", "ChangeLog"))))
246
247         self.mock_changelog((_changelog3, _changelog4))
248         checkout = self.mock_checkout_for_test()
249         commit_message = checkout.commit_message_for_this_commit(git_commit=None, return_stderr=True)
250         # Throw away the first line - a warning about unknown VCS root.
251         commit_message.message_lines = commit_message.message_lines[1:]
252         self.assertMultiLineEqual(commit_message.message(), expected_commit_message)
253
254     def test_commit_message_for_changelogs_with_different_messages(self):
255         expected_commit_message = u"""SECTORDER_FLAGS should be defined in target's xcconfig file, not Base.xcconfig
256 <http://webkit.org/b/135006>
257
258 Patch by David Kilzer <ddkilzer@apple.com> on 2014-07-17
259 Reviewed by Darin Adler.
260
261 Source/WebKitLegacy:
262
263 * WebKit.xcodeproj/project.pbxproj: Remove references to unused
264 WebKit.xcconfig file.
265
266 LayoutTests:
267
268 Filler change.
269
270 * Path/To/Complicated/File: Added.
271 """
272
273         self.changelog_paths = list(map(self.filesystem.abspath, (self.filesystem.join("Source/WebKitLegacy", "ChangeLog"), self.filesystem.join("LayoutTests", "ChangeLog"))))
274
275         self.mock_changelog((_changelog3, _changelog5))
276         checkout = self.mock_checkout_for_test()
277         commit_message = checkout.commit_message_for_this_commit(git_commit=None, return_stderr=True)
278         # Throw away the first line - a warning about unknown VCS root.
279         commit_message.message_lines = commit_message.message_lines[1:]
280         self.assertMultiLineEqual(commit_message.message(), expected_commit_message)
281
282     def test_commit_message_for_one_revert_changelog(self):
283         expected_commit_message = u"""Reverting r170340 and r170339.
284
285 Changeset r170339 broke the Apple Windows Debug and Release builds.
286
287 Reverted changesets:
288
289 "[Win] Build fix after r134209"
290 https://trac.webkit.org/changeset/170340
291
292 "[Win] Clean up and refactor WinLauncher"
293 https://bugs.webkit.org/show_bug.cgi?id=134209
294 https://trac.webkit.org/changeset/170339
295
296 Patch by Daniel Bates <dabates@apple.com> on 2014-06-23
297 """
298
299         self.changelog_paths = list(map(self.filesystem.abspath, [self.filesystem.join("Tools", "ChangeLog")]))
300
301         self.mock_changelog([_changelog6])
302         checkout = self.mock_checkout_for_test()
303         commit_message = checkout.commit_message_for_this_commit(git_commit=None, return_stderr=True)
304         # Throw away the first line - a warning about unknown VCS root.
305         commit_message.message_lines = commit_message.message_lines[1:]
306         self.assertMultiLineEqual(commit_message.message(), expected_commit_message)
307
308     def test_commit_message_for_revert_changelogs_with_different_directories(self):
309         expected_commit_message = u"""Reverting r170340 and r170339.
310
311 Changeset r170339 broke the Apple Windows Debug and Release builds.
312
313 Reverted changesets:
314
315 "[Win] Build fix after r134209"
316 https://trac.webkit.org/changeset/170340
317
318 "[Win] Clean up and refactor WinLauncher"
319 https://bugs.webkit.org/show_bug.cgi?id=134209
320 https://trac.webkit.org/changeset/170339
321
322 Patch by Daniel Bates <dabates@apple.com> on 2014-06-23
323 """
324
325         self.changelog_paths = list(map(self.filesystem.abspath, (self.filesystem.join("Tools", "ChangeLog"), self.filesystem.join("Source/WebCore", "ChangeLog"))))
326
327         self.mock_changelog((_changelog6, _changelog6))
328         checkout = self.mock_checkout_for_test()
329         commit_message = checkout.commit_message_for_this_commit(git_commit=None, return_stderr=True)
330         # Throw away the first line - a warning about unknown VCS root.
331         commit_message.message_lines = commit_message.message_lines[1:]
332         self.assertMultiLineEqual(commit_message.message(), expected_commit_message)
333
334
335 class CheckoutTest(unittest.TestCase):
336     def _make_checkout(self):
337         return Checkout(scm=MockSCM(), filesystem=MockFileSystem(), executive=MockExecutive())
338
339     def test_latest_entry_for_changelog_at_revision(self):
340         def mock_contents_at_revision(changelog_path, revision):
341             self.assertEqual(changelog_path, "foo")
342             self.assertEqual(revision, "bar")
343             # contents_at_revision is expected to return a byte array (str)
344             # so we encode our unicode ChangeLog down to a utf-8 stream.
345             # The ChangeLog utf-8 decoding should ignore invalid codepoints.
346             invalid_utf8 = b'\255'
347             return encode_if_necessary(_changelog1) + invalid_utf8
348         checkout = self._make_checkout()
349         checkout._scm.contents_at_revision = mock_contents_at_revision
350         entry = checkout._latest_entry_for_changelog_at_revision("foo", "bar")
351         self.assertMultiLineEqual(entry.contents(), _changelog1entry1)  # Pylint is confused about this line, pylint: disable=E1101
352
353     # FIXME: This tests a hack around our current changed_files handling.
354     # Right now changelog_entries_for_revision tries to fetch deleted files
355     # from revisions, resulting in a ScriptError exception.  Test that we
356     # recover from those and still return the other ChangeLog entries.
357     def test_changelog_entries_for_revision(self):
358         checkout = self._make_checkout()
359         checkout._scm.changed_files_for_revision = lambda revision: ['foo/ChangeLog', 'bar/ChangeLog']
360
361         def mock_latest_entry_for_changelog_at_revision(path, revision):
362             if path == "foo/ChangeLog":
363                 return 'foo'
364             raise ScriptError()
365
366         checkout._latest_entry_for_changelog_at_revision = mock_latest_entry_for_changelog_at_revision
367
368         # Even though fetching one of the entries failed, the other should succeed.
369         entries = checkout.changelog_entries_for_revision(1)
370         self.assertEqual(len(entries), 1)
371         self.assertEqual(entries[0], 'foo')
372
373     def test_commit_info_for_revision(self):
374         checkout = self._make_checkout()
375         checkout._scm.changed_files_for_revision = lambda revision: ['path/to/file', 'another/file']
376         checkout._scm.committer_email_for_revision = lambda revision, changed_files=None: "committer@example.com"
377         checkout.changelog_entries_for_revision = lambda revision, changed_files=None: [ChangeLogEntry(_changelog1entry1)]
378         commitinfo = checkout.commit_info_for_revision(4)
379         self.assertEqual(commitinfo.bug_id(), 36629)
380         self.assertEqual(commitinfo.bug_description(), "Unreviewed build fix to un-break webkit-patch land.")
381         self.assertEqual(commitinfo.author_name(), u"Fr\u00e9d\u00e9ric Wang")
382         self.assertEqual(commitinfo.author_email(), "fred.wang@free.fr")
383         self.assertIsNone(commitinfo.reviewer_text())
384         self.assertIsNone(commitinfo.reviewer())
385         self.assertEqual(commitinfo.committer_email(), "committer@example.com")
386         self.assertIsNone(commitinfo.committer())
387         self.assertEqual(commitinfo.to_json(), {
388             'bug_id': 36629,
389             'author_email': 'fred.wang@free.fr',
390             'changed_files': [
391                 'path/to/file',
392                 'another/file',
393             ],
394             'reviewer_text': None,
395             'author_name': u'Fr\u00e9d\u00e9ric Wang',
396             'bug_description': 'Unreviewed build fix to un-break webkit-patch land.',
397         })
398
399         checkout.changelog_entries_for_revision = lambda revision, changed_files=None: []
400         self.assertIsNone(checkout.commit_info_for_revision(1))
401
402     def test_bug_id_for_revision(self):
403         checkout = self._make_checkout()
404         checkout._scm.committer_email_for_revision = lambda revision: "committer@example.com"
405         checkout.changelog_entries_for_revision = lambda revision, changed_files=None: [ChangeLogEntry(_changelog1entry1)]
406         self.assertEqual(checkout.bug_id_for_revision(4), 36629)
407
408     def test_bug_id_for_this_commit(self):
409         checkout = self._make_checkout()
410         checkout.commit_message_for_this_commit = lambda git_commit, changed_files=None, return_stderr=False: CommitMessage(ChangeLogEntry(_changelog1entry1).contents().splitlines())
411         self.assertEqual(checkout.bug_id_for_this_commit(git_commit=None), 36629)
412
413     def test_modified_changelogs(self):
414         checkout = self._make_checkout()
415         checkout._scm.checkout_root = "/foo/bar"
416         checkout._scm.changed_files = lambda git_commit: ["file1", "ChangeLog", "relative/path/ChangeLog"]
417         expected_changlogs = ["/foo/bar/ChangeLog", "/foo/bar/relative/path/ChangeLog"]
418         self.assertEqual(checkout.modified_changelogs(git_commit=None), expected_changlogs)
419
420     def test_suggested_reviewers(self):
421         def mock_changelog_entries_for_revision(revision, changed_files=None):
422             if revision == 27:
423                 return []
424             if revision % 2 == 0:
425                 return [ChangeLogEntry(_changelog1entry1)]
426             return [ChangeLogEntry(_changelog1entry2)]
427
428         def mock_revisions_changing_file(path, limit=5):
429             if path.endswith('ChangeLog'):
430                 return [3]
431             if path.endswith('file_with_empty_changelog'):
432                 return [27]
433             return [4, 8]
434
435         checkout = self._make_checkout()
436         checkout._scm.checkout_root = '/foo/bar'
437         checkout._scm.changed_files = lambda git_commit: ['file1', 'file2', 'relative/path/ChangeLog', 'file_with_empty_changelog']
438         checkout._scm.revisions_changing_file = mock_revisions_changing_file
439         checkout.changelog_entries_for_revision = mock_changelog_entries_for_revision
440         reviewers = checkout.suggested_reviewers(git_commit=None)
441         reviewer_names = [reviewer.full_name for reviewer in reviewers]
442         self.assertEqual(reviewer_names, [u'Fr\u00e9d\u00e9ric Wang'])
443
444     def test_apply_patch(self):
445         checkout = self._make_checkout()
446         checkout._executive = MockExecutive(should_log=True)
447         checkout.script_path = lambda script: script
448         mock_patch = Mock()
449         mock_patch.contents = lambda: "foo"
450         mock_patch.reviewer = lambda: None
451         expected_logs = "MOCK run_command: ['svn-apply', '--force'], cwd=/mock-checkout, input=foo\n"
452         OutputCapture().assert_outputs(self, checkout.apply_patch, [mock_patch], expected_logs=expected_logs)