• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env vpython3
2
3# Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
4#
5# Use of this source code is governed by a BSD-style license
6# that can be found in the LICENSE file in the root of the source
7# tree. An additional intellectual property rights grant can be found
8# in the file PATENTS.  All contributing project authors may
9# be found in the AUTHORS file in the root of the source tree.
10
11
12import glob
13import os
14import shutil
15import sys
16import tempfile
17import unittest
18import mock
19
20SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
21PARENT_DIR = os.path.join(SCRIPT_DIR, os.pardir)
22sys.path.append(PARENT_DIR)
23
24import roll_deps
25from roll_deps import CalculateChangedDeps, FindAddedDeps, \
26  FindRemovedDeps, ChooseCQMode, GenerateCommitMessage, \
27  GetMatchingDepsEntries, ParseDepsDict, ParseLocalDepsFile, UpdateDepsFile, \
28  ChromiumRevisionUpdate
29
30
31TEST_DATA_VARS = {
32    'chromium_git': 'https://chromium.googlesource.com',
33    'chromium_revision': '1b9c098a08e40114e44b6c1ec33ddf95c40b901d',
34}
35
36DEPS_ENTRIES = {
37    'src/build': 'https://build.com',
38    'src/third_party/depot_tools': 'https://depottools.com',
39    'src/testing/gtest': 'https://gtest.com',
40    'src/testing/gmock': 'https://gmock.com',
41}
42
43BUILD_OLD_REV = '52f7afeca991d96d68cf0507e20dbdd5b845691f'
44BUILD_NEW_REV = 'HEAD'
45DEPOTTOOLS_OLD_REV = 'b9ae2ca9a55d9b754c313f4c9e9f0f3b804a5e44'
46DEPOTTOOLS_NEW_REV = '1206a353e40abb70d8454eb9af53db0ad10b713c'
47
48NO_CHROMIUM_REVISION_UPDATE = ChromiumRevisionUpdate('cafe', 'cafe')
49
50
51class TestError(Exception):
52  pass
53
54
55class FakeCmd:
56  def __init__(self):
57    self.expectations = []
58
59  def AddExpectation(self, *args, **kwargs):
60    returns = kwargs.pop('_returns', None)
61    ignores = kwargs.pop('_ignores', [])
62    self.expectations.append((args, kwargs, returns, ignores))
63
64  def __call__(self, *args, **kwargs):
65    if not self.expectations:
66      raise TestError('Got unexpected\n%s\n%s' % (args, kwargs))
67    exp_args, exp_kwargs, exp_returns, ignores = self.expectations.pop(0)
68    for item in ignores:
69      kwargs.pop(item, None)
70    if args != exp_args or kwargs != exp_kwargs:
71      message = 'Expected:\n  args: %s\n  kwargs: %s\n' % (exp_args, exp_kwargs)
72      message += 'Got:\n  args: %s\n  kwargs: %s\n' % (args, kwargs)
73      raise TestError(message)
74    return exp_returns
75
76
77class NullCmd:
78  """No-op mock when calls mustn't be checked. """
79
80  def __call__(self, *args, **kwargs):
81    # Empty stdout and stderr.
82    return None, None
83
84
85class TestRollChromiumRevision(unittest.TestCase):
86  def setUp(self):
87    self._output_dir = tempfile.mkdtemp()
88    test_data_dir = os.path.join(SCRIPT_DIR, 'testdata', 'roll_deps')
89    for test_file in glob.glob(os.path.join(test_data_dir, '*')):
90      shutil.copy(test_file, self._output_dir)
91    join = lambda f: os.path.join(self._output_dir, f)
92    self._webrtc_depsfile = join('DEPS')
93    self._new_cr_depsfile = join('DEPS.chromium.new')
94    self._webrtc_depsfile_android = join('DEPS.with_android_deps')
95    self._new_cr_depsfile_android = join('DEPS.chromium.with_android_deps')
96    self.fake = FakeCmd()
97
98  def tearDown(self):
99    shutil.rmtree(self._output_dir, ignore_errors=True)
100    self.assertEqual(self.fake.expectations, [])
101
102  def testVarLookup(self):
103    local_scope = {'foo': 'wrong', 'vars': {'foo': 'bar'}}
104    lookup = roll_deps.VarLookup(local_scope)
105    self.assertEqual(lookup('foo'), 'bar')
106
107  def testUpdateDepsFile(self):
108    new_rev = 'aaaaabbbbbcccccdddddeeeeefffff0000011111'
109    current_rev = TEST_DATA_VARS['chromium_revision']
110
111    with open(self._new_cr_depsfile_android, 'rb') as deps_file:
112      new_cr_contents = deps_file.read().decode('utf-8')
113
114    UpdateDepsFile(self._webrtc_depsfile,
115                   ChromiumRevisionUpdate(current_rev, new_rev), [],
116                   new_cr_contents)
117    with open(self._webrtc_depsfile, 'rb') as deps_file:
118      deps_contents = deps_file.read().decode('utf-8')
119      self.assertTrue(new_rev in deps_contents,
120                      'Failed to find %s in\n%s' % (new_rev, deps_contents))
121
122  def _UpdateDepsSetup(self):
123    with open(self._webrtc_depsfile_android, 'rb') as deps_file:
124      webrtc_contents = deps_file.read().decode('utf-8')
125    with open(self._new_cr_depsfile_android, 'rb') as deps_file:
126      new_cr_contents = deps_file.read().decode('utf-8')
127    webrtc_deps = ParseDepsDict(webrtc_contents)
128    new_cr_deps = ParseDepsDict(new_cr_contents)
129
130    changed_deps = CalculateChangedDeps(webrtc_deps, new_cr_deps)
131    with mock.patch('roll_deps._RunCommand', NullCmd()):
132      UpdateDepsFile(self._webrtc_depsfile_android, NO_CHROMIUM_REVISION_UPDATE,
133                     changed_deps, new_cr_contents)
134
135    with open(self._webrtc_depsfile_android, 'rb') as deps_file:
136      updated_contents = deps_file.read().decode('utf-8')
137
138    return webrtc_contents, updated_contents
139
140  def testUpdateAndroidGeneratedDeps(self):
141    _, updated_contents = self._UpdateDepsSetup()
142
143    changed = 'third_party/android_deps/libs/android_arch_core_common'
144    changed_version = '1.0.0-cr0'
145    self.assertTrue(changed in updated_contents)
146    self.assertTrue(changed_version in updated_contents)
147
148  def testAddAndroidGeneratedDeps(self):
149    webrtc_contents, updated_contents = self._UpdateDepsSetup()
150
151    added = 'third_party/android_deps/libs/android_arch_lifecycle_common'
152    self.assertFalse(added in webrtc_contents)
153    self.assertTrue(added in updated_contents)
154
155  def testRemoveAndroidGeneratedDeps(self):
156    webrtc_contents, updated_contents = self._UpdateDepsSetup()
157
158    removed = 'third_party/android_deps/libs/android_arch_lifecycle_runtime'
159    self.assertTrue(removed in webrtc_contents)
160    self.assertFalse(removed in updated_contents)
161
162  def testParseDepsDict(self):
163    with open(self._webrtc_depsfile, 'rb') as deps_file:
164      deps_contents = deps_file.read().decode('utf-8')
165    local_scope = ParseDepsDict(deps_contents)
166    vars_dict = local_scope['vars']
167
168    def AssertVar(variable_name):
169      self.assertEqual(vars_dict[variable_name], TEST_DATA_VARS[variable_name])
170
171    AssertVar('chromium_git')
172    AssertVar('chromium_revision')
173    self.assertEqual(len(local_scope['deps']), 3)
174    self.assertEqual(len(local_scope['deps_os']), 1)
175
176  def testGetMatchingDepsEntriesReturnsPathInSimpleCase(self):
177    entries = GetMatchingDepsEntries(DEPS_ENTRIES, 'src/testing/gtest')
178    self.assertEqual(len(entries), 1)
179    self.assertEqual(entries[0], DEPS_ENTRIES['src/testing/gtest'])
180
181  def testGetMatchingDepsEntriesHandlesSimilarStartingPaths(self):
182    entries = GetMatchingDepsEntries(DEPS_ENTRIES, 'src/testing')
183    self.assertEqual(len(entries), 2)
184
185  def testGetMatchingDepsEntriesHandlesTwoPathsWithIdenticalFirstParts(self):
186    entries = GetMatchingDepsEntries(DEPS_ENTRIES, 'src/build')
187    self.assertEqual(len(entries), 1)
188
189  def testCalculateChangedDeps(self):
190    webrtc_deps = ParseLocalDepsFile(self._webrtc_depsfile)
191    new_cr_deps = ParseLocalDepsFile(self._new_cr_depsfile)
192    with mock.patch('roll_deps._RunCommand', self.fake):
193      _SetupGitLsRemoteCall(
194          self.fake, 'https://chromium.googlesource.com/chromium/src/build',
195          BUILD_NEW_REV)
196      changed_deps = CalculateChangedDeps(webrtc_deps, new_cr_deps)
197
198    self.assertEqual(len(changed_deps), 3)
199    self.assertEqual(changed_deps[0].path, 'src/build')
200    self.assertEqual(changed_deps[0].current_rev, BUILD_OLD_REV)
201    self.assertEqual(changed_deps[0].new_rev, BUILD_NEW_REV)
202
203    self.assertEqual(changed_deps[1].path, 'src/buildtools/linux64')
204    self.assertEqual(changed_deps[1].package, 'gn/gn/linux-amd64')
205    self.assertEqual(changed_deps[1].current_version,
206                     'git_revision:69ec4fca1fa69ddadae13f9e6b7507efa0675263')
207    self.assertEqual(changed_deps[1].new_version, 'git_revision:new-revision')
208
209    self.assertEqual(changed_deps[2].path, 'src/third_party/depot_tools')
210    self.assertEqual(changed_deps[2].current_rev, DEPOTTOOLS_OLD_REV)
211    self.assertEqual(changed_deps[2].new_rev, DEPOTTOOLS_NEW_REV)
212
213  def testWithDistinctDeps(self):
214    """Check CalculateChangedDeps works when deps are added/removed."""
215    webrtc_deps = ParseLocalDepsFile(self._webrtc_depsfile_android)
216    new_cr_deps = ParseLocalDepsFile(self._new_cr_depsfile_android)
217    changed_deps = CalculateChangedDeps(webrtc_deps, new_cr_deps)
218    self.assertEqual(len(changed_deps), 1)
219    self.assertEqual(
220        changed_deps[0].path,
221        'src/third_party/android_deps/libs/android_arch_core_common')
222    self.assertEqual(
223        changed_deps[0].package,
224        'chromium/third_party/android_deps/libs/android_arch_core_common')
225    self.assertEqual(changed_deps[0].current_version, 'version:0.9.0')
226    self.assertEqual(changed_deps[0].new_version, 'version:1.0.0-cr0')
227
228  def testFindAddedDeps(self):
229    webrtc_deps = ParseLocalDepsFile(self._webrtc_depsfile_android)
230    new_cr_deps = ParseLocalDepsFile(self._new_cr_depsfile_android)
231    added_android_paths, other_paths = FindAddedDeps(webrtc_deps, new_cr_deps)
232    self.assertEqual(
233        added_android_paths,
234        ['src/third_party/android_deps/libs/android_arch_lifecycle_common'])
235    self.assertEqual(other_paths, [])
236
237  def testFindRemovedDeps(self):
238    webrtc_deps = ParseLocalDepsFile(self._webrtc_depsfile_android)
239    new_cr_deps = ParseLocalDepsFile(self._new_cr_depsfile_android)
240    removed_android_paths, other_paths = FindRemovedDeps(
241        webrtc_deps, new_cr_deps)
242    self.assertEqual(
243        removed_android_paths,
244        ['src/third_party/android_deps/libs/android_arch_lifecycle_runtime'])
245    self.assertEqual(other_paths, [])
246
247  def testMissingDepsIsDetected(self):
248    """Check error is reported when deps cannot be automatically removed."""
249    # The situation at test is the following:
250    #   * A WebRTC DEPS entry is missing from Chromium.
251    #   * The dependency isn't an android_deps (those are supported).
252    webrtc_deps = ParseLocalDepsFile(self._webrtc_depsfile)
253    new_cr_deps = ParseLocalDepsFile(self._new_cr_depsfile_android)
254    _, other_paths = FindRemovedDeps(webrtc_deps, new_cr_deps)
255    self.assertEqual(other_paths,
256                     ['src/buildtools/linux64', 'src/third_party/depot_tools'])
257
258  def testExpectedDepsIsNotReportedMissing(self):
259    """Some deps musn't be seen as missing, even if absent from Chromium."""
260    webrtc_deps = ParseLocalDepsFile(self._webrtc_depsfile)
261    new_cr_deps = ParseLocalDepsFile(self._new_cr_depsfile_android)
262    removed_android_paths, other_paths = FindRemovedDeps(
263        webrtc_deps, new_cr_deps)
264    self.assertTrue('src/build' not in removed_android_paths)
265    self.assertTrue('src/build' not in other_paths)
266
267  def _CommitMessageSetup(self):
268    webrtc_deps = ParseLocalDepsFile(self._webrtc_depsfile_android)
269    new_cr_deps = ParseLocalDepsFile(self._new_cr_depsfile_android)
270
271    changed_deps = CalculateChangedDeps(webrtc_deps, new_cr_deps)
272    added_paths, _ = FindAddedDeps(webrtc_deps, new_cr_deps)
273    removed_paths, _ = FindRemovedDeps(webrtc_deps, new_cr_deps)
274
275    current_commit_pos = 'cafe'
276    new_commit_pos = 'f00d'
277
278    commit_msg = GenerateCommitMessage(NO_CHROMIUM_REVISION_UPDATE,
279                                       current_commit_pos, new_commit_pos,
280                                       changed_deps, added_paths, removed_paths)
281
282    return [l.strip() for l in commit_msg.split('\n')]
283
284  def testChangedDepsInCommitMessage(self):
285    commit_lines = self._CommitMessageSetup()
286
287    changed = '* src/third_party/android_deps/libs/' \
288              'android_arch_core_common: version:0.9.0..version:1.0.0-cr0'
289    self.assertTrue(changed in commit_lines)
290    # Check it is in adequate section.
291    changed_line = commit_lines.index(changed)
292    self.assertTrue('Changed' in commit_lines[changed_line - 1])
293
294  def testAddedDepsInCommitMessage(self):
295    commit_lines = self._CommitMessageSetup()
296
297    added = '* src/third_party/android_deps/libs/' \
298            'android_arch_lifecycle_common'
299    self.assertTrue(added in commit_lines)
300    # Check it is in adequate section.
301    added_line = commit_lines.index(added)
302    self.assertTrue('Added' in commit_lines[added_line - 1])
303
304  def testRemovedDepsInCommitMessage(self):
305    commit_lines = self._CommitMessageSetup()
306
307    removed = '* src/third_party/android_deps/libs/' \
308              'android_arch_lifecycle_runtime'
309    self.assertTrue(removed in commit_lines)
310    # Check it is in adequate section.
311    removed_line = commit_lines.index(removed)
312    self.assertTrue('Removed' in commit_lines[removed_line - 1])
313
314
315class TestChooseCQMode(unittest.TestCase):
316  def testSkip(self):
317    self.assertEqual(ChooseCQMode(True, 99, 500000, 500100), 0)
318
319  def testDryRun(self):
320    self.assertEqual(ChooseCQMode(False, 101, 500000, 500100), 1)
321
322  def testSubmit(self):
323    self.assertEqual(ChooseCQMode(False, 100, 500000, 500100), 2)
324
325
326class TestReadUrlContent(unittest.TestCase):
327  def setUp(self):
328    self.url = 'http://localhost+?format=TEXT'
329
330  def testReadUrlContent(self):
331    url_mock = mock.Mock()
332    roll_deps.urllib.request.urlopen = url_mock
333
334    roll_deps.ReadUrlContent(self.url)
335
336    calls = [
337        mock.call('http://localhost+?format=TEXT'),
338        mock.call().readlines(),
339        mock.call().close()
340    ]
341    self.assertEqual(url_mock.mock_calls, calls)
342
343  def testReadUrlContentError(self):
344    roll_deps.logging = mock.Mock()
345
346    readlines_mock = mock.Mock()
347    readlines_mock.readlines = mock.Mock(
348        side_effect=IOError('Connection error'))
349    readlines_mock.close = mock.Mock()
350
351    url_mock = mock.Mock(return_value=readlines_mock)
352    roll_deps.urllib.request.urlopen = url_mock
353
354    try:
355      roll_deps.ReadUrlContent(self.url)
356    except OSError:
357      self.assertTrue(roll_deps.logging.exception.called)
358
359
360def _SetupGitLsRemoteCall(cmd_fake, url, revision):
361  cmd = ['git', 'ls-remote', url, revision]
362  cmd_fake.AddExpectation(cmd, _returns=(revision, None))
363
364
365if __name__ == '__main__':
366  unittest.main()
367