diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 619a312e72..6d8f3304dd 100755 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -231,6 +231,25 @@ def _CheckUnwantedDependencies(input_api, output_api): return results +def _RunPythonTests(input_api, output_api): + def join(*args): + return input_api.os_path.join(input_api.PresubmitLocalPath(), *args) + + test_directories = [ + join('tools', 'autoroller', 'unittests'), + ] + + tests = [] + for directory in test_directories: + tests.extend( + input_api.canned_checks.GetUnitTestsInDirectory( + input_api, + output_api, + directory, + whitelist=[r'.+_test\.py$'])) + return input_api.RunTests(tests, parallel=True) + + def _CommonChecks(input_api, output_api): """Checks common to both upload and commit.""" results = [] @@ -277,6 +296,7 @@ def _CommonChecks(input_api, output_api): results.extend(_CheckNoFRIEND_TEST(input_api, output_api)) results.extend(_CheckGypChanges(input_api, output_api)) results.extend(_CheckUnwantedDependencies(input_api, output_api)) + results.extend(_RunPythonTests(input_api, output_api)) return results diff --git a/tools/autoroller/cl_description.py b/tools/autoroller/cl_description.py deleted file mode 100755 index 95e2f2c8e9..0000000000 --- a/tools/autoroller/cl_description.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python -# Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. -# -# Use of this source code is governed by a BSD-style license -# that can be found in the LICENSE file in the root of the source -# tree. An additional intellectual property rights grant can be found -# in the file PATENTS. All contributing project authors may -# be found in the AUTHORS file in the root of the source tree. - -"""Creates a CL description for auto-rolling chromium_revision in WebRTC.""" - -import argparse -import base64 -import collections -import os -import re -import sys -import urllib - - -CHROMIUM_LKGR_URL = 'https://chromium-status.appspot.com/lkgr' -CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src' -CHROMIUM_COMMIT_TEMPLATE = CHROMIUM_SRC_URL + '/+/%s' -CHROMIUM_FILE_TEMPLATE = CHROMIUM_SRC_URL + '/+/%s/%s' - -GIT_NUMBER_RE = re.compile('^Cr-Commit-Position: .*#([0-9]+).*$') -CLANG_REVISION_RE = re.compile(r'^CLANG_REVISION=(\d+)$') -SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) -CHECKOUT_ROOT_DIR = os.path.join(SCRIPT_DIR, os.pardir, os.pardir) -sys.path.append(CHECKOUT_ROOT_DIR) -import setup_links - -sys.path.append(os.path.join(CHECKOUT_ROOT_DIR, 'tools')) -import find_depot_tools -find_depot_tools.add_depot_tools_to_path() -from gclient import GClientKeywords - -CLANG_UPDATE_SCRIPT_URL_PATH = 'tools/clang/scripts/update.sh' -CLANG_UPDATE_SCRIPT_LOCAL_PATH = os.path.join('tools', 'clang', 'scripts', - 'update.sh') - -DepsEntry = collections.namedtuple('DepsEntry', 'path url revision') -ChangedDep = collections.namedtuple('ChangedDep', 'path current_rev new_rev') - - -def parse_deps_dict(deps_content): - local_scope = {} - var = GClientKeywords.VarImpl({}, local_scope) - global_scope = { - 'File': GClientKeywords.FileImpl, - 'From': GClientKeywords.FromImpl, - 'Var': var.Lookup, - 'deps_os': {}, - } - exec(deps_content, global_scope, local_scope) - return local_scope - - -def parse_local_deps_file(filename): - with open(filename, 'rb') as f: - deps_content = f.read() - return parse_deps_dict(deps_content) - - -def parse_remote_cr_deps_file(revision): - deps_content = read_remote_cr_file('DEPS', revision) - return parse_deps_dict(deps_content) - -def parse_git_number(commit_message): - for line in reversed(commit_message.splitlines()): - m = GIT_NUMBER_RE.match(line.strip()) - if m: - return m.group(1) - print 'Failed to parse svn revision id from:\n%s\n' % commit_message - sys.exit(-1) - -def _read_gittiles_content(url): - # Download and decode BASE64 content until - # https://code.google.com/p/gitiles/issues/detail?id=7 is fixed. - base64_content = read_url_content(url + '?format=TEXT') - return base64.b64decode(base64_content[0]) - -def read_remote_cr_file(path_below_src, revision): - """Reads a remote Chromium file of a specific revision. Returns a string.""" - return _read_gittiles_content(CHROMIUM_FILE_TEMPLATE % (revision, - path_below_src)) - -def read_remote_cr_commit(revision): - """Reads a remote Chromium commit message. Returns a string.""" - return _read_gittiles_content(CHROMIUM_COMMIT_TEMPLATE % revision) - -def read_url_content(url): - """Connect to a remote host and read the contents. Returns a list of lines.""" - try: - conn = urllib.urlopen(url) - return conn.readlines() - except IOError as e: - print >> sys.stderr, 'Error connecting to %s. Error: ' % url, e - return None - finally: - conn.close() - - -def get_matching_deps_entries(depsentry_dict, dir_path): - """Gets all deps entries matching the provided path - - This list may contain more than one DepsEntry object. - Example: dir_path='src/testing' would give results containing both - 'src/testing/gtest' and 'src/testing/gmock' deps entries for Chromium's DEPS. - - Returns: - A list DepsEntry objects. - """ - result = [] - for path, depsentry in depsentry_dict.iteritems(): - if (path == dir_path or - path.startswith(dir_path) and path[len(dir_path):][0] == '/'): - result.append(depsentry) - return result - -def build_depsentry_dict(deps_dict): - """Builds a dict of DepsEntry object from a raw parsed deps dict.""" - result = {} - def add_depsentries(deps_subdict): - for path, deps_url in deps_subdict.iteritems(): - if not result.has_key(path): - url, revision = deps_url.split('@') if deps_url else (None, None) - result[path] = DepsEntry(path, url, revision) - - add_depsentries(deps_dict['deps']) - for deps_os in ['win', 'mac', 'unix', 'android', 'ios', 'unix']: - add_depsentries(deps_dict['deps_os'].get(deps_os, {})) - return result - -def calculate_changed_deps(current_deps, new_deps): - result = [] - current_entries = build_depsentry_dict(current_deps) - new_entries = build_depsentry_dict(new_deps) - - all_deps_dirs = setup_links.DIRECTORIES - for deps_dir in all_deps_dirs: - # All deps have 'src' prepended to the path in the Chromium DEPS file. - dir_path = 'src/%s' % deps_dir - - for entry in get_matching_deps_entries(current_entries, dir_path): - new_matching_entries = get_matching_deps_entries(new_entries, entry.path) - assert len(new_matching_entries) <= 1, ( - 'Should never find more than one entry matching %s in %s, found %d' % - (entry.path, new_entries, len(new_matching_entries))) - if not new_matching_entries: - result.append(ChangedDep(entry.path, entry.revision, 'None')) - elif entry != new_matching_entries[0]: - result.append(ChangedDep(entry.path, entry.revision, - new_matching_entries[0].revision)) - return result - - -def calculate_changed_clang(new_cr_rev): - def get_clang_rev(lines): - for line in lines: - match = CLANG_REVISION_RE.match(line) - if match: - return match.group(1) - return None - - chromium_src_path = os.path.join(CHECKOUT_ROOT_DIR, 'chromium', 'src', - CLANG_UPDATE_SCRIPT_LOCAL_PATH) - with open(chromium_src_path, 'rb') as f: - current_lines = f.readlines() - current_rev = get_clang_rev(current_lines) - - new_clang_update_sh = read_remote_cr_file(CLANG_UPDATE_SCRIPT_URL_PATH, - new_cr_rev).splitlines() - new_rev = get_clang_rev(new_clang_update_sh) - return ChangedDep(CLANG_UPDATE_SCRIPT_LOCAL_PATH, current_rev, new_rev) - - -def generate_commit_message(current_cr_rev, new_cr_rev, changed_deps_list, - clang_change): - current_cr_rev = current_cr_rev[0:7] - new_cr_rev = new_cr_rev[0:7] - rev_interval = '%s..%s' % (current_cr_rev, new_cr_rev) - - current_git_number = parse_git_number(read_remote_cr_commit(current_cr_rev)) - new_git_number = parse_git_number(read_remote_cr_commit(new_cr_rev)) - git_number_interval = '%s:%s' % (current_git_number, new_git_number) - - commit_msg = ['Roll chromium_revision %s (%s)' % (rev_interval, - git_number_interval)] - - if changed_deps_list: - commit_msg.append('\nRelevant changes:') - - for c in changed_deps_list: - commit_msg.append('* %s: %s..%s' % (c.path, c.current_rev[0:7], - c.new_rev[0:7])) - - change_url = CHROMIUM_FILE_TEMPLATE % (rev_interval, 'DEPS') - commit_msg.append('Details: %s' % change_url) - - if clang_change.current_rev != clang_change.new_rev: - commit_msg.append('\nClang version changed %s:%s' % - (clang_change.current_rev, clang_change.new_rev)) - change_url = CHROMIUM_FILE_TEMPLATE % (rev_interval, - CLANG_UPDATE_SCRIPT_URL_PATH) - commit_msg.append('Details: %s' % change_url) - else: - commit_msg.append('\nClang version was not updated in this roll.') - return commit_msg - - -def main(): - p = argparse.ArgumentParser() - p.add_argument('-r', '--revision', - help=('Chromium Git revision to roll to. Defaults to the ' - 'Chromium LKGR revision if omitted.')) - opts = p.parse_args() - - if not opts.revision: - lkgr_contents = read_url_content(CHROMIUM_LKGR_URL) - print 'No revision specified. Using LKGR: %s' % lkgr_contents[0] - opts.revision = lkgr_contents[0] - - local_deps = parse_local_deps_file(os.path.join(CHECKOUT_ROOT_DIR, 'DEPS')) - current_cr_rev = local_deps['vars']['chromium_revision'] - - current_cr_deps = parse_remote_cr_deps_file(current_cr_rev) - new_cr_deps = parse_remote_cr_deps_file(opts.revision) - - changed_deps = sorted(calculate_changed_deps(current_cr_deps, new_cr_deps)) - clang_change = calculate_changed_clang(opts.revision) - if changed_deps or clang_change: - commit_msg = generate_commit_message(current_cr_rev, opts.revision, - changed_deps, clang_change) - print '\n'.join(commit_msg) - else: - print ('No deps changes detected when rolling from %s to %s. Aborting ' - 'without action.') % (current_cr_rev, opts.revision,) - return 0 - -if __name__ == '__main__': - sys.exit(main()) diff --git a/tools/autoroller/roll_chromium_revision.py b/tools/autoroller/roll_chromium_revision.py new file mode 100755 index 0000000000..46acdade4d --- /dev/null +++ b/tools/autoroller/roll_chromium_revision.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python +# Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +"""Script to roll chromium_revision in the WebRTC DEPS file.""" + +import argparse +import base64 +import collections +import logging +import os +import re +import subprocess +import sys +import urllib + + +CHROMIUM_LKGR_URL = 'https://chromium-status.appspot.com/lkgr' +CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src' +CHROMIUM_COMMIT_TEMPLATE = CHROMIUM_SRC_URL + '/+/%s' +CHROMIUM_FILE_TEMPLATE = CHROMIUM_SRC_URL + '/+/%s/%s' + +COMMIT_POSITION_RE = re.compile('^Cr-Commit-Position: .*#([0-9]+).*$') +CLANG_REVISION_RE = re.compile(r'^CLANG_REVISION=(\d+)$') +ROLL_BRANCH_NAME = 'roll_chromium_revision' + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +CHECKOUT_ROOT_DIR = os.path.realpath(os.path.join(SCRIPT_DIR, os.pardir, + os.pardir)) +sys.path.append(CHECKOUT_ROOT_DIR) +import setup_links + +sys.path.append(os.path.join(CHECKOUT_ROOT_DIR, 'tools')) +import find_depot_tools +find_depot_tools.add_depot_tools_to_path() +from gclient import GClientKeywords + +CLANG_UPDATE_SCRIPT_URL_PATH = 'tools/clang/scripts/update.sh' +CLANG_UPDATE_SCRIPT_LOCAL_PATH = os.path.join('tools', 'clang', 'scripts', + 'update.sh') + +DepsEntry = collections.namedtuple('DepsEntry', 'path url revision') +ChangedDep = collections.namedtuple('ChangedDep', 'path current_rev new_rev') + + +def ParseDepsDict(deps_content): + local_scope = {} + var = GClientKeywords.VarImpl({}, local_scope) + global_scope = { + 'File': GClientKeywords.FileImpl, + 'From': GClientKeywords.FromImpl, + 'Var': var.Lookup, + 'deps_os': {}, + } + exec(deps_content, global_scope, local_scope) + return local_scope + + +def ParseLocalDepsFile(filename): + with open(filename, 'rb') as f: + deps_content = f.read() + return ParseDepsDict(deps_content) + + +def ParseRemoteCrDepsFile(revision): + deps_content = ReadRemoteCrFile('DEPS', revision) + return ParseDepsDict(deps_content) + + +def ParseCommitPosition(commit_message): + for line in reversed(commit_message.splitlines()): + m = COMMIT_POSITION_RE.match(line.strip()) + if m: + return m.group(1) + logging.error('Failed to parse commit position id from:\n%s\n', + commit_message) + sys.exit(-1) + + +def _RunCommand(command, working_dir=None, ignore_exit_code=False, + extra_env=None): + """Runs a command and returns the output from that command. + + If the command fails (exit code != 0), the function will exit the process. + + Returns: + A tuple containing the stdout and stderr outputs as strings. + """ + working_dir = working_dir or CHECKOUT_ROOT_DIR + logging.debug('CMD: %s CWD: %s', ' '.join(command), working_dir) + env = os.environ.copy() + if extra_env: + assert all(type(value) == str for value in extra_env.values()) + logging.debug('extra env: %s', extra_env) + env.update(extra_env) + p = subprocess.Popen(command, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env, + cwd=working_dir, universal_newlines=True) + std_output = p.stdout.read() + err_output = p.stderr.read() + p.wait() + p.stdout.close() + p.stderr.close() + if not ignore_exit_code and p.returncode != 0: + logging.error('Command failed: %s\n' + 'stdout:\n%s\n' + 'stderr:\n%s\n', ' '.join(command), std_output, err_output) + sys.exit(p.returncode) + return std_output, err_output + + +def _GetBranches(): + """Returns a tuple of active,branches. + + The 'active' is the name of the currently active branch and 'branches' is a + list of all branches. + """ + lines = _RunCommand(['git', 'branch'])[0].split('\n') + branches = [] + active = '' + for line in lines: + if '*' in line: + # The assumption is that the first char will always be the '*'. + active = line[1:].strip() + branches.append(active) + else: + branch = line.strip() + if branch: + branches.append(branch) + return active, branches + + +def _ReadGitilesContent(url): + # Download and decode BASE64 content until + # https://code.google.com/p/gitiles/issues/detail?id=7 is fixed. + base64_content = ReadUrlContent(url + '?format=TEXT') + return base64.b64decode(base64_content[0]) + + +def ReadRemoteCrFile(path_below_src, revision): + """Reads a remote Chromium file of a specific revision. Returns a string.""" + return _ReadGitilesContent(CHROMIUM_FILE_TEMPLATE % (revision, + path_below_src)) + + +def ReadRemoteCrCommit(revision): + """Reads a remote Chromium commit message. Returns a string.""" + return _ReadGitilesContent(CHROMIUM_COMMIT_TEMPLATE % revision) + + +def ReadUrlContent(url): + """Connect to a remote host and read the contents. Returns a list of lines.""" + conn = urllib.urlopen(url) + try: + return conn.readlines() + except IOError as e: + logging.exception('Error connecting to %s. Error: %s', url, e) + raise + finally: + conn.close() + + +def GetMatchingDepsEntries(depsentry_dict, dir_path): + """Gets all deps entries matching the provided path. + + This list may contain more than one DepsEntry object. + Example: dir_path='src/testing' would give results containing both + 'src/testing/gtest' and 'src/testing/gmock' deps entries for Chromium's DEPS. + Example 2: dir_path='src/build' should return 'src/build' but not + 'src/buildtools'. + + Returns: + A list of DepsEntry objects. + """ + result = [] + for path, depsentry in depsentry_dict.iteritems(): + if path == dir_path: + result.append(depsentry) + else: + parts = path.split(os.sep) + if all(part == parts[i] + for i, part in enumerate(dir_path.split(os.sep))): + result.append(depsentry) + return result + + +def BuildDepsentryDict(deps_dict): + """Builds a dict of DepsEntry object from a raw parsed deps dict.""" + result = {} + def AddDepsEntries(deps_subdict): + for path, deps_url in deps_subdict.iteritems(): + if not result.has_key(path): + url, revision = deps_url.split('@') if deps_url else (None, None) + result[path] = DepsEntry(path, url, revision) + + AddDepsEntries(deps_dict['deps']) + for deps_os in ['win', 'mac', 'unix', 'android', 'ios', 'unix']: + AddDepsEntries(deps_dict['deps_os'].get(deps_os, {})) + return result + + +def CalculateChangedDeps(current_deps, new_deps): + result = [] + current_entries = BuildDepsentryDict(current_deps) + new_entries = BuildDepsentryDict(new_deps) + + all_deps_dirs = setup_links.DIRECTORIES + for deps_dir in all_deps_dirs: + # All deps have 'src' prepended to the path in the Chromium DEPS file. + dir_path = 'src/%s' % deps_dir + + for entry in GetMatchingDepsEntries(current_entries, dir_path): + new_matching_entries = GetMatchingDepsEntries(new_entries, entry.path) + assert len(new_matching_entries) <= 1, ( + 'Should never find more than one entry matching %s in %s, found %d' % + (entry.path, new_entries, len(new_matching_entries))) + if not new_matching_entries: + result.append(ChangedDep(entry.path, entry.revision, 'None')) + elif entry != new_matching_entries[0]: + result.append(ChangedDep(entry.path, entry.revision, + new_matching_entries[0].revision)) + return result + + +def CalculateChangedClang(new_cr_rev): + def GetClangRev(lines): + for line in lines: + match = CLANG_REVISION_RE.match(line) + if match: + return match.group(1) + return None + + chromium_src_path = os.path.join(CHECKOUT_ROOT_DIR, 'chromium', 'src', + CLANG_UPDATE_SCRIPT_LOCAL_PATH) + with open(chromium_src_path, 'rb') as f: + current_lines = f.readlines() + current_rev = GetClangRev(current_lines) + + new_clang_update_sh = ReadRemoteCrFile(CLANG_UPDATE_SCRIPT_URL_PATH, + new_cr_rev).splitlines() + new_rev = GetClangRev(new_clang_update_sh) + return ChangedDep(CLANG_UPDATE_SCRIPT_LOCAL_PATH, current_rev, new_rev) + + +def GenerateCommitMessage(current_cr_rev, new_cr_rev, changed_deps_list, + clang_change): + current_cr_rev = current_cr_rev[0:7] + new_cr_rev = new_cr_rev[0:7] + rev_interval = '%s..%s' % (current_cr_rev, new_cr_rev) + + current_git_number = ParseCommitPosition(ReadRemoteCrCommit(current_cr_rev)) + new_git_number = ParseCommitPosition(ReadRemoteCrCommit(new_cr_rev)) + git_number_interval = '%s:%s' % (current_git_number, new_git_number) + + commit_msg = ['Roll chromium_revision %s (%s)' % (rev_interval, + git_number_interval)] + + if changed_deps_list: + commit_msg.append('\nRelevant changes:') + + for c in changed_deps_list: + commit_msg.append('* %s: %s..%s' % (c.path, c.current_rev[0:7], + c.new_rev[0:7])) + + change_url = CHROMIUM_FILE_TEMPLATE % (rev_interval, 'DEPS') + commit_msg.append('Details: %s' % change_url) + + if clang_change.current_rev != clang_change.new_rev: + commit_msg.append('\nClang version changed %s:%s' % + (clang_change.current_rev, clang_change.new_rev)) + change_url = CHROMIUM_FILE_TEMPLATE % (rev_interval, + CLANG_UPDATE_SCRIPT_URL_PATH) + commit_msg.append('Details: %s' % change_url) + else: + commit_msg.append('\nClang version was not updated in this roll.') + return '\n'.join(commit_msg) + + +def UpdateDeps(deps_filename, old_cr_revision, new_cr_revision): + """Update the DEPS file with the new revision.""" + with open(deps_filename, 'rb') as deps_file: + deps_content = deps_file.read() + deps_content = deps_content.replace(old_cr_revision, new_cr_revision) + with open(deps_filename, 'wb') as deps_file: + deps_file.write(deps_content) + + +def _CreateRollBranch(dry_run): + current_branch = _RunCommand( + ['git', 'rev-parse', '--abbrev-ref', 'HEAD'])[0].splitlines()[0] + if current_branch != 'master': + logging.error('Please checkout the master branch and re-run this script.') + if not dry_run: + sys.exit(-1) + + logging.info('Creating roll branch: %s', ROLL_BRANCH_NAME) + if not dry_run: + _RunCommand(['git', 'checkout', '-b', ROLL_BRANCH_NAME]) + + +def _RemovePreviousRollBranch(dry_run): + active_branch, branches = _GetBranches() + if active_branch == ROLL_BRANCH_NAME: + active_branch = 'master' + if ROLL_BRANCH_NAME in branches: + logging.info('Removing previous roll branch (%s)', ROLL_BRANCH_NAME) + if not dry_run: + _RunCommand(['git', 'checkout', active_branch]) + _RunCommand(['git', 'branch', '-D', ROLL_BRANCH_NAME]) + + +def _LocalCommit(commit_msg, dry_run): + logging.info('Committing changes locally.') + if not dry_run: + _RunCommand(['git', 'add', '--update', '.']) + _RunCommand(['git', 'commit', '-m', commit_msg]) + + +def _UploadCL(dry_run): + logging.info('Uploading CL...') + if not dry_run: + _RunCommand(['git', 'cl', 'upload'], extra_env={'EDITOR': 'true'}) + + +def _LaunchTrybots(dry_run): + logging.info('Sending tryjobs...') + if not dry_run: + _RunCommand(['git', 'cl', 'try']) + + +def main(): + p = argparse.ArgumentParser() + p.add_argument('--clean', action='store_true', default=False, + help='Removes any previous local roll branch.') + p.add_argument('-r', '--revision', + help=('Chromium Git revision to roll to. Defaults to the ' + 'Chromium LKGR revision if omitted.')) + p.add_argument('--dry-run', action='store_true', default=False, + help=('Calculate changes and modify DEPS, but don\'t create ' + 'any local branch, commit, upload CL or send any ' + 'tryjobs.')) + p.add_argument('-v', '--verbose', action='store_true', default=False, + help='Be extra verbose in printing of log messages.') + opts = p.parse_args() + + if opts.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + if opts.clean: + _RemovePreviousRollBranch(opts.dry_run) + + if not opts.revision: + lkgr_contents = ReadUrlContent(CHROMIUM_LKGR_URL) + logging.info('No revision specified. Using LKGR: %s', lkgr_contents[0]) + opts.revision = lkgr_contents[0] + + deps_filename = os.path.join(CHECKOUT_ROOT_DIR, 'DEPS') + local_deps = ParseLocalDepsFile(deps_filename) + current_cr_rev = local_deps['vars']['chromium_revision'] + + current_cr_deps = ParseRemoteCrDepsFile(current_cr_rev) + new_cr_deps = ParseRemoteCrDepsFile(opts.revision) + + changed_deps = sorted(CalculateChangedDeps(current_cr_deps, new_cr_deps)) + clang_change = CalculateChangedClang(opts.revision) + if changed_deps or clang_change: + commit_msg = GenerateCommitMessage(current_cr_rev, opts.revision, + changed_deps, clang_change) + logging.debug('Commit message:\n%s', commit_msg) + else: + logging.info('No deps changes detected when rolling from %s to %s. ' + 'Aborting without action.', current_cr_rev, opts.revision) + return 0 + + _CreateRollBranch(opts.dry_run) + UpdateDeps(deps_filename, current_cr_rev, opts.revision) + _LocalCommit(commit_msg, opts.dry_run) + _UploadCL(opts.dry_run) + _LaunchTrybots(opts.dry_run) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/autoroller/unittests/DEPS b/tools/autoroller/unittests/DEPS new file mode 100644 index 0000000000..71f01ad827 --- /dev/null +++ b/tools/autoroller/unittests/DEPS @@ -0,0 +1,7 @@ +# Sample DEPS file for testing. + +vars = { + 'extra_gyp_flag': '-Dextra_gyp_flag=0', + 'chromium_git': 'https://chromium.googlesource.com', + 'chromium_revision': '1b9c098a08e40114e44b6c1ec33ddf95c40b901d', +} diff --git a/tools/autoroller/unittests/roll_chromium_revision_test.py b/tools/autoroller/unittests/roll_chromium_revision_test.py new file mode 100755 index 0000000000..cb3c776a84 --- /dev/null +++ b/tools/autoroller/unittests/roll_chromium_revision_test.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +import os +import shutil +import sys +import tempfile +import unittest + + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +PARENT_DIR = os.path.join(SCRIPT_DIR, os.pardir) +sys.path.append(PARENT_DIR) +from roll_chromium_revision import ParseDepsDict, UpdateDeps, \ + GetMatchingDepsEntries + +TEST_DATA_VARS = { + 'extra_gyp_flag': '-Dextra_gyp_flag=0', + 'chromium_git': 'https://chromium.googlesource.com', + 'chromium_revision': '1b9c098a08e40114e44b6c1ec33ddf95c40b901d', +} + +DEPS_ENTRIES = { + 'src/build': 'https://build.com', + 'src/buildtools': 'https://buildtools.com', + 'src/testing/gtest': 'https://gtest.com', + 'src/testing/gmock': 'https://gmock.com', +} + + +class TestRollChromiumRevision(unittest.TestCase): + def setUp(self): + self._output_dir = tempfile.mkdtemp() + shutil.copy(os.path.join(SCRIPT_DIR, 'DEPS'), self._output_dir) + self._deps_filename = os.path.join(self._output_dir, 'DEPS') + + def tearDown(self): + shutil.rmtree(self._output_dir, ignore_errors=True) + + def testUpdateDeps(self): + new_rev = 'aaaaabbbbbcccccdddddeeeeefffff0000011111' + + current_rev = TEST_DATA_VARS['chromium_revision'] + UpdateDeps(self._deps_filename, current_rev, new_rev) + with open(self._deps_filename) as deps_file: + deps_contents = deps_file.read() + self.assertTrue(new_rev in deps_contents, + 'Failed to find %s in\n%s' % (new_rev, deps_contents)) + + def testParseDepsDict(self): + with open(self._deps_filename) as deps_file: + deps_contents = deps_file.read() + local_scope = ParseDepsDict(deps_contents) + vars_dict = local_scope['vars'] + + def assertVar(variable_name): + self.assertEquals(vars_dict[variable_name], TEST_DATA_VARS[variable_name]) + assertVar('extra_gyp_flag') + assertVar('chromium_git') + assertVar('chromium_revision') + + def testGetMatchingDepsEntriesReturnsPathInSimpleCase(self): + entries = GetMatchingDepsEntries(DEPS_ENTRIES, 'src/testing/gtest') + self.assertEquals(len(entries), 1) + self.assertEquals(entries[0], DEPS_ENTRIES['src/testing/gtest']) + + def testGetMatchingDepsEntriesHandlesSimilarStartingPaths(self): + entries = GetMatchingDepsEntries(DEPS_ENTRIES, 'src/testing') + self.assertEquals(len(entries), 2) + + def testGetMatchingDepsEntriesHandlesTwoPathsWithIdenticalFirstParts(self): + entries = GetMatchingDepsEntries(DEPS_ENTRIES, 'src/build') + self.assertEquals(len(entries), 1) + self.assertEquals(entries[0], DEPS_ENTRIES['src/build']) + +if __name__ == '__main__': + unittest.main()