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