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