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