351 lines
13 KiB
Python
351 lines
13 KiB
Python
#!/usr/bin/env vpython3
|
|
# Copyright (c) 2018 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import logging
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, ROOT_DIR)
|
|
|
|
import roll_dep
|
|
from testing_support import fake_repos
|
|
|
|
ROLL_DEP = os.path.join(ROOT_DIR, 'roll-dep')
|
|
GCLIENT = os.path.join(ROOT_DIR, 'gclient')
|
|
|
|
# TODO: Should fix these warnings.
|
|
# pylint: disable=line-too-long
|
|
|
|
def create_deps_content(git_base, path_to_revision_map):
|
|
"""
|
|
Create a DEPS file content string with the given dependency mappings.
|
|
|
|
Args:
|
|
git_base: The base URL for git repositories
|
|
path_to_revision_map: Dictionary mapping dependency paths to their revisions
|
|
|
|
Returns:
|
|
String with the complete DEPS file content including standard hooks
|
|
"""
|
|
dep_lines = []
|
|
git_base = git_base.replace('\\', '\\\\')
|
|
for path, revision in path_to_revision_map.items():
|
|
dep_lines.append(f' "{path}": "file://{git_base}repo_2@{revision}",')
|
|
|
|
# Combine all parts with standard hooks.
|
|
deps_content = [
|
|
'deps = {',
|
|
'\n'.join(dep_lines),
|
|
'}',
|
|
'hooks = [',
|
|
' {"action": ["foo", "--android", "{checkout_android}"]}',
|
|
']',
|
|
]
|
|
return '\n'.join(deps_content)
|
|
|
|
|
|
class FakeRepos(fake_repos.FakeReposBase):
|
|
NB_GIT_REPOS = 2
|
|
|
|
def populateGit(self):
|
|
for x in range(1,4):
|
|
self._commit_git('repo_2', {'origin': f'git/repo_2@{x}'})
|
|
|
|
# repo_2@1 is the default revision.
|
|
# Anything under 'third_party/not_supported' tests handling unsupported
|
|
# cases.
|
|
repo2_revision = self.git_hashes['repo_2'][1][0]
|
|
self._commit_git(
|
|
'repo_1', {
|
|
'DEPS': create_deps_content(self.git_base, {
|
|
'src/foo': repo2_revision,
|
|
'src/third_party/repo_2/src': repo2_revision,
|
|
'src/third_party/repo_2B/src': repo2_revision,
|
|
'src/third_party/not_supported/with_divider/src': repo2_revision,
|
|
'src/third_party/not_supported/multiple_revisions/src': repo2_revision,
|
|
'src/third_party/not_supported/no_revision/src': repo2_revision
|
|
}),
|
|
'README.chromium': '\n'.join([
|
|
'Name: test repo',
|
|
'URL: https://example.com',
|
|
'Version: 1.0',
|
|
'Revision: abcabc123123',
|
|
'License: MIT',
|
|
]),
|
|
'third_party/repo_2/README.chromium': '\n'.join([
|
|
'Name: test repo 2',
|
|
'URL: https://example.com',
|
|
'Version: 1.0',
|
|
'Revision: abc1234',
|
|
'License: MIT',
|
|
]),
|
|
'third_party/repo_2B/README.chromium': '\n'.join([
|
|
'Name: Override DEPS value for revision',
|
|
'URL: https://example.com',
|
|
'Version: 1.0',
|
|
'Revision: DEPS',
|
|
'License: MIT',
|
|
]),
|
|
'third_party/not_supported/with_divider/README.chromium': '\n'.join([
|
|
'Name: Deps divider not supported',
|
|
'URL: https://example.com',
|
|
'Version: 1.0',
|
|
'Revision: abc1234',
|
|
'License: MIT',
|
|
'-------------------- DEPENDENCY DIVIDER --------------------',
|
|
'Name: So nothing here should change',
|
|
'URL: https://example.com',
|
|
'Version: 1.0',
|
|
'Revision: abc1234',
|
|
'License: MIT',
|
|
]),
|
|
'third_party/not_supported/multiple_revisions/README.chromium': '\n'.join([
|
|
'Name: Multiple revisions',
|
|
'URL: https://example.com',
|
|
'Version: 1.0',
|
|
'Revision: abc1234',
|
|
'License: MIT',
|
|
'Revision: abc1235', # This should not happen.
|
|
]),
|
|
'third_party/not_supported/no_revision/README.chromium': '\n'.join([
|
|
'Name: No revision',
|
|
'URL: https://example.com',
|
|
'Version: 1.0',
|
|
'License: MIT',
|
|
]),
|
|
})
|
|
|
|
|
|
class RollDepTest(fake_repos.FakeReposTestBase):
|
|
FAKE_REPOS_CLASS = FakeRepos
|
|
|
|
def setUp(self):
|
|
super(RollDepTest, self).setUp()
|
|
# Make sure it doesn't try to auto update when testing!
|
|
self.env = os.environ.copy()
|
|
self.env['DEPOT_TOOLS_UPDATE'] = '0'
|
|
self.env['DEPOT_TOOLS_METRICS'] = '0'
|
|
# Suppress Python 3 warnings and other test undesirables.
|
|
self.env['GCLIENT_TEST'] = '1'
|
|
|
|
self.maxDiff = None
|
|
|
|
self.enabled = self.FAKE_REPOS.set_up_git()
|
|
self.src_dir = os.path.join(self.root_dir, 'src')
|
|
self.foo_dir = os.path.join(self.src_dir, 'foo')
|
|
self.all_repos = [
|
|
'src/foo',
|
|
'src/third_party/repo_2/src',
|
|
'src/third_party/repo_2B/src',
|
|
'src/third_party/not_supported/with_divider/src',
|
|
'src/third_party/not_supported/multiple_revisions/src',
|
|
'src/third_party/not_supported/no_revision/src',
|
|
]
|
|
if self.enabled:
|
|
self.call([
|
|
GCLIENT, 'config', 'file://' + self.git_base + 'repo_1',
|
|
'--name', 'src'
|
|
],
|
|
cwd=self.root_dir)
|
|
self.call([GCLIENT, 'sync'], cwd=self.root_dir)
|
|
|
|
def call(self, cmd, cwd=None):
|
|
cwd = cwd or self.src_dir
|
|
process = subprocess.Popen(cmd,
|
|
cwd=cwd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
env=self.env,
|
|
shell=sys.platform.startswith('win'))
|
|
stdout, stderr = process.communicate()
|
|
logging.debug("XXX: %s\n%s\nXXX" % (' '.join(cmd), stdout))
|
|
logging.debug("YYY: %s\n%s\nYYY" % (' '.join(cmd), stderr))
|
|
stdout = stdout.decode('utf-8')
|
|
stderr = stderr.decode('utf-8')
|
|
return (stdout.replace('\r\n',
|
|
'\n'), stderr.replace('\r\n',
|
|
'\n'), process.returncode)
|
|
|
|
def assert_deps_match(self, expected_path_to_revision_map):
|
|
# Assume everything is at the default revision and only update the
|
|
# provided paths.
|
|
default_revision = self.githash('repo_2', 1)
|
|
expected_map = {path: default_revision for path in self.all_repos}
|
|
expected_map.update(expected_path_to_revision_map)
|
|
|
|
for path, revision in expected_map.items():
|
|
with self.subTest(path=path):
|
|
path_dir = os.path.join(self.root_dir, path)
|
|
self.assertEqual(self.gitrevparse(path_dir), revision)
|
|
|
|
with open(os.path.join(self.src_dir, 'DEPS')) as f:
|
|
actual_content = f.read()
|
|
with self.subTest(path='DEPS'):
|
|
expected_content = create_deps_content(self.git_base,expected_map)
|
|
self.assertEqual(expected_content, actual_content)
|
|
|
|
|
|
def testRollsDep(self):
|
|
if not self.enabled:
|
|
return
|
|
stdout, stderr, returncode = self.call([ROLL_DEP]+self.all_repos)
|
|
latest_revision = self.githash('repo_2', 3)
|
|
|
|
self.assertEqual(stderr, '')
|
|
self.assertEqual(returncode, 0)
|
|
|
|
# All deps should be rolled to the latest revision.
|
|
self.assert_deps_match({p: latest_revision for p in self.all_repos})
|
|
|
|
commit_message = self.call(['git', 'log', '-n', '1'])[0]
|
|
|
|
expected_message = 'Roll src/foo/ %s..%s (2 commits)' % (self.githash(
|
|
'repo_2', 1)[:9], latest_revision[:9])
|
|
|
|
self.assertIn(expected_message, stdout)
|
|
self.assertIn(expected_message, commit_message)
|
|
|
|
|
|
def testRollsDepWithReadme(self):
|
|
"""Tests roll-dep when updating README.chromium files."""
|
|
if not self.enabled:
|
|
return
|
|
stdout, stderr, returncode = self.call(
|
|
[ROLL_DEP, '--update-readme']+self.all_repos
|
|
)
|
|
latest_revision = self.githash('repo_2', 3)
|
|
|
|
# All deps should be rolled to the latest revision (3).
|
|
self.assert_deps_match({p: latest_revision for p in self.all_repos})
|
|
self.assertEqual(stderr, '')
|
|
self.assertEqual(returncode, 0)
|
|
for path in self.all_repos:
|
|
with self.subTest(path=path):
|
|
contents = ''
|
|
readme_path = os.path.join(self.root_dir, path, os.path.pardir, 'README.chromium')
|
|
if os.path.exists(readme_path):
|
|
with open(readme_path, 'r') as f:
|
|
contents = f.read()
|
|
if path == 'src/third_party/not_supported/no_revision/src':
|
|
self.assertIn('README.chromium contains 0 Revision: lines', stdout)
|
|
if 'not_supported' in path:
|
|
self.assertNotIn(latest_revision, contents)
|
|
continue
|
|
# Check that the revision was updated.
|
|
self.assertIn(f'Revision: {latest_revision}', contents)
|
|
self.assertNotIn('Revision: abcabc123123', contents)
|
|
self.assertNotIn('No README.chromium found', stdout)
|
|
|
|
def testRollsDepReviewers(self):
|
|
if not self.enabled:
|
|
return
|
|
|
|
stdout, stderr, returncode = self.call([
|
|
ROLL_DEP, 'src/foo', '-r', 'foo@example.com', '-r',
|
|
'bar@example.com,baz@example.com'
|
|
])
|
|
|
|
self.assertEqual(stderr, '')
|
|
self.assertEqual(returncode, 0)
|
|
|
|
expected_message = 'R=foo@example.com,bar@example.com,baz@example.com'
|
|
|
|
self.assertIn(expected_message, stdout)
|
|
|
|
def testRollsDepToSpecificRevision(self):
|
|
if not self.enabled:
|
|
return
|
|
specified_revision = self.githash('repo_2', 2)
|
|
stdout, stderr, returncode = self.call(
|
|
[ROLL_DEP, 'src/foo', '--roll-to', specified_revision])
|
|
|
|
self.assertEqual(stderr, '')
|
|
self.assertEqual(returncode, 0)
|
|
|
|
self.assert_deps_match({
|
|
'src/foo': specified_revision,
|
|
})
|
|
|
|
commit_message = self.call(['git', 'log', '-n', '1'])[0]
|
|
|
|
expected_message = 'Roll src/foo/ %s..%s (1 commit)' % (self.githash(
|
|
'repo_2', 1)[:9], self.githash('repo_2', 2)[:9])
|
|
|
|
self.assertIn(expected_message, stdout)
|
|
self.assertIn(expected_message, commit_message)
|
|
|
|
def testRollsDepLogLimit(self):
|
|
if not self.enabled:
|
|
return
|
|
stdout, stderr, returncode = self.call(
|
|
[ROLL_DEP, 'src/foo', '--log-limit', '1'])
|
|
latest_revision = self.githash('repo_2', 3)
|
|
|
|
self.assertEqual(stderr, '')
|
|
self.assertEqual(returncode, 0)
|
|
self.assert_deps_match({
|
|
'src/foo':latest_revision,
|
|
})
|
|
|
|
commit_message = self.call(['git', 'log', '-n', '1'])[0]
|
|
|
|
expected_message = 'Roll src/foo/ %s..%s (2 commits)' % (self.githash(
|
|
'repo_2', 1)[:9], self.githash('repo_2', 3)[:9])
|
|
|
|
self.assertIn(expected_message, stdout)
|
|
self.assertIn(expected_message, commit_message)
|
|
|
|
|
|
class CommitMessageTest(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.logs = '\n'.join([
|
|
'2024-04-05 alice Goodbye',
|
|
'2024-04-03 bob Hello World',
|
|
])
|
|
|
|
# Mock the `git log` call.
|
|
mock.patch('roll_dep.check_output', return_value=self.logs).start()
|
|
self.addCleanup(mock.patch.stopall)
|
|
|
|
def testShowShortLog(self):
|
|
message = roll_dep.generate_commit_message(
|
|
'/path/to/dir', 'dep', 'abc', 'def',
|
|
'https://chromium.googlesource.com', True, 10)
|
|
|
|
self.assertIn('Roll dep/ abc..def (2 commits)', message)
|
|
self.assertIn('$ git log', message)
|
|
self.assertIn(self.logs, message)
|
|
|
|
def testHideShortLog(self):
|
|
message = roll_dep.generate_commit_message(
|
|
'/path/to/dir', 'dep', 'abc', 'def',
|
|
'https://chromium.googlesource.com', False, 10)
|
|
|
|
self.assertNotIn('$ git log', message)
|
|
self.assertNotIn(self.logs, message)
|
|
|
|
def testShouldShowLogWithPublicHost(self):
|
|
self.assertTrue(
|
|
roll_dep.should_show_log(
|
|
'https://chromium.googlesource.com/project'))
|
|
|
|
def testShouldNotShowLogWithPrivateHost(self):
|
|
self.assertFalse(
|
|
roll_dep.should_show_log(
|
|
'https://private.googlesource.com/project'))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
level = logging.DEBUG if '-v' in sys.argv else logging.FATAL
|
|
logging.basicConfig(level=level,
|
|
format='%(asctime).19s %(levelname)s %(filename)s:'
|
|
'%(lineno)s %(message)s')
|
|
unittest.main()
|