blob: 14ee17449c3a32761c75dc10ea738c082dd0ea31 [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012 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.
"""Smoke tests for gclient.py.
Shell out 'gclient' and run basic conformance tests.
This test assumes GClientSmokeBase.URL_BASE is valid.
"""
import json
import logging
import os
import re
import subprocess
import sys
import unittest
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT_DIR)
import gclient_utils
import scm as gclient_scm
import subprocess2
from testing_support import fake_repos
from testing_support.fake_repos import join, write
GCLIENT_PATH = os.path.join(ROOT_DIR, 'gclient')
COVERAGE = False
class GClientSmokeBase(fake_repos.FakeReposTestBase):
def setUp(self):
super(GClientSmokeBase, 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'
def gclient(self, cmd, cwd=None):
if not cwd:
cwd = self.root_dir
if COVERAGE:
# Don't use the wrapper script.
cmd_base = ['coverage', 'run', '-a', GCLIENT_PATH + '.py']
else:
cmd_base = [GCLIENT_PATH]
cmd = cmd_base + cmd
process = subprocess.Popen(cmd, cwd=cwd, env=self.env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=sys.platform.startswith('win'),
universal_newlines=True)
(stdout, stderr) = process.communicate()
logging.debug("XXX: %s\n%s\nXXX" % (' '.join(cmd), stdout))
logging.debug("YYY: %s\n%s\nYYY" % (' '.join(cmd), stderr))
return (stdout.replace('\r\n', '\n'), stderr.replace('\r\n', '\n'),
process.returncode)
def untangle(self, stdout):
"""Separates output based on thread IDs."""
tasks = {}
remaining = []
task_id = 0
for line in stdout.splitlines(False):
m = re.match(r'^(\d)+>(.*)$', line)
if not m:
if task_id:
# Lines broken with carriage breaks don't have a thread ID, but belong
# to the last seen thread ID.
tasks.setdefault(task_id, []).append(line)
else:
remaining.append(line)
else:
self.assertEqual([], remaining)
task_id = int(m.group(1))
tasks.setdefault(task_id, []).append(m.group(2))
out = []
for key in sorted(tasks.keys()):
out.extend(tasks[key])
out.extend(remaining)
return '\n'.join(out)
def parseGclient(self, cmd, items, expected_stderr='', untangle=False):
"""Parse gclient's output to make it easier to test.
If untangle is True, tries to sort out the output from parallel checkout."""
(stdout, stderr, returncode) = self.gclient(cmd)
if untangle:
stdout = self.untangle(stdout)
self.checkString(expected_stderr, stderr)
self.assertEqual(0, returncode)
return self.checkBlock(stdout, items)
def splitBlock(self, stdout):
"""Split gclient's output into logical execution blocks.
___ running 'foo' at '/bar'
(...)
___ running 'baz' at '/bar'
(...)
will result in 2 items of len((...).splitlines()) each.
"""
results = []
for line in stdout.splitlines(False):
# Intentionally skips empty lines.
if not line:
continue
if not line.startswith('__'):
if results:
results[-1].append(line)
else:
# TODO(maruel): gclient's git stdout is inconsistent.
# This should fail the test instead!!
pass
continue
match = re.match(r'^________ ([a-z]+) \'(.*)\' in \'(.*)\'$', line)
if match:
results.append([[match.group(1), match.group(2), match.group(3)]])
continue
match = re.match(r'^_____ (.*) is missing, synching instead$', line)
if match:
# Blah, it's when a dependency is deleted, we should probably not
# output this message.
results.append([line])
continue
# These two regexps are a bit too broad, they are necessary only for git
# checkouts.
if (re.match(r'_____ [^ ]+ at [^ ]+', line) or
re.match(r'_____ [^ ]+ : Attempting rebase onto [0-9a-f]+...', line)):
continue
# Fail for any unrecognized lines that start with '__'.
self.fail(line)
return results
def checkBlock(self, stdout, items):
results = self.splitBlock(stdout)
for i in range(min(len(results), len(items))):
if isinstance(items[i], (list, tuple)):
verb = items[i][0]
path = items[i][1]
else:
verb = items[i]
path = self.root_dir
self.checkString(results[i][0][0], verb, (i, results[i][0][0], verb))
if sys.platform == 'win32':
# Make path lower case since casing can change randomly.
self.checkString(
results[i][0][2].lower(),
path.lower(),
(i, results[i][0][2].lower(), path.lower()))
else:
self.checkString(results[i][0][2], path, (i, results[i][0][2], path))
self.assertEqual(len(results), len(items), (stdout, items, len(results)))
return results
class GClientSmoke(GClientSmokeBase):
"""Doesn't require git-daemon."""
@property
def git_base(self):
return 'git://random.server/git/'
def testNotConfigured(self):
res = ('', 'Error: client not configured; see \'gclient config\'\n', 1)
self.check(res, self.gclient(['diff']))
self.check(res, self.gclient(['pack']))
self.check(res, self.gclient(['revert']))
self.check(res, self.gclient(['revinfo']))
self.check(res, self.gclient(['runhooks']))
self.check(res, self.gclient(['status']))
self.check(res, self.gclient(['sync']))
self.check(res, self.gclient(['update']))
def testConfig(self):
# Get any bootstrapping out of the way.
results = self.gclient(['version'])
self.assertEqual(results[2], 0)
p = join(self.root_dir, '.gclient')
def test(cmd, expected):
if os.path.exists(p):
os.remove(p)
results = self.gclient(cmd)
self.check(('', '', 0), results)
mode = 'r' if sys.version_info.major == 3 else 'rU'
with open(p, mode) as f:
self.checkString(expected, f.read())
test(['config', self.git_base + 'src/'],
('solutions = [\n'
' { "name" : "src",\n'
' "url" : "%ssrc",\n'
' "deps_file" : "DEPS",\n'
' "managed" : True,\n'
' "custom_deps" : {\n'
' },\n'
' "custom_vars": {},\n'
' },\n'
']\n' % self.git_base))
test(['config', self.git_base + 'repo_1', '--name', 'src',
'--cache-dir', 'none'],
('solutions = [\n'
' { "name" : "src",\n'
' "url" : "%srepo_1",\n'
' "deps_file" : "DEPS",\n'
' "managed" : True,\n'
' "custom_deps" : {\n'
' },\n'
' "custom_vars": {},\n'
' },\n'
']\n'
'cache_dir = None\n') % self.git_base)
test(['config', 'https://example.com/foo', 'faa',
'--cache-dir', 'something'],
'solutions = [\n'
' { "name" : "foo",\n'
' "url" : "https://example.com/foo",\n'
' "deps_file" : "DEPS",\n'
' "managed" : True,\n'
' "custom_deps" : {\n'
' },\n'
' "custom_vars": {},\n'
' },\n'
']\n'
'cache_dir = \'something\'\n')
test(['config', 'https://example.com/foo', '--deps', 'blah'],
'solutions = [\n'
' { "name" : "foo",\n'
' "url" : "https://example.com/foo",\n'
' "deps_file" : "blah",\n'
' "managed" : True,\n'
' "custom_deps" : {\n'
' },\n'
' "custom_vars": {},\n'
' },\n'
']\n')
test(['config', self.git_base + 'src/',
'--custom-var', 'bool_var=True',
'--custom-var', 'str_var="abc"'],
('solutions = [\n'
' { "name" : "src",\n'
' "url" : "%ssrc",\n'
' "deps_file" : "DEPS",\n'
' "managed" : True,\n'
' "custom_deps" : {\n'
' },\n'
' "custom_vars": {\'bool_var\': True, \'str_var\': \'abc\'},\n'
' },\n'
']\n') % self.git_base)
test(['config', '--spec', '["blah blah"]'], '["blah blah"]')
os.remove(p)
results = self.gclient(['config', 'foo', 'faa', 'fuu'])
err = ('Usage: gclient.py config [options] [url]\n\n'
'gclient.py: error: Inconsistent arguments. Use either --spec or one'
' or 2 args\n')
self.check(('', err, 2), results)
self.assertFalse(os.path.exists(join(self.root_dir, '.gclient')))
def testSolutionNone(self):
results = self.gclient(['config', '--spec',
'solutions=[{"name": "./", "url": None}]'])
self.check(('', '', 0), results)
results = self.gclient(['sync'])
self.check(('', '', 0), results)
self.assertTree({})
results = self.gclient(['revinfo'])
self.check(('./: None\n', '', 0), results)
self.check(('', '', 0), self.gclient(['diff']))
self.assertTree({})
self.check(('', '', 0), self.gclient(['pack']))
self.check(('', '', 0), self.gclient(['revert']))
self.assertTree({})
self.check(('', '', 0), self.gclient(['runhooks']))
self.assertTree({})
self.check(('', '', 0), self.gclient(['status']))
def testDifferentTopLevelDirectory(self):
# Check that even if the .gclient file does not mention the directory src
# itself, but it is included via dependencies, the .gclient file is used.
self.gclient(['config', self.git_base + 'src.DEPS'])
deps = join(self.root_dir, 'src.DEPS')
os.mkdir(deps)
subprocess2.check_output(['git', 'init'], cwd=deps)
write(join(deps, 'DEPS'),
'deps = { "src": "%ssrc" }' % (self.git_base))
subprocess2.check_output(['git', 'add', 'DEPS'], cwd=deps)
subprocess2.check_output(
['git', 'commit', '-a', '-m', 'DEPS file'], cwd=deps)
src = join(self.root_dir, 'src')
os.mkdir(src)
subprocess2.check_output(['git', 'init'], cwd=src)
res = self.gclient(['status', '--jobs', '1', '-v'], src)
self.checkBlock(res[0], [('running', deps), ('running', src)])
class GClientSmokeGIT(GClientSmokeBase):
def setUp(self):
super(GClientSmokeGIT, self).setUp()
self.enabled = self.FAKE_REPOS.set_up_git()
def testSync(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
# Test unversioned checkout.
self.parseGclient(
['sync', '--deps', 'mac', '--jobs', '1'],
['running', 'running', 'running'])
# TODO(maruel): http://crosbug.com/3582 hooks run even if not matching, must
# add sync parsing to get the list of updated files.
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
# Manually remove git_hooked1 before synching to make sure it's not
# recreated.
os.remove(join(self.root_dir, 'src', 'git_hooked1'))
# Test incremental versioned sync: sync backward.
self.parseGclient(
['sync', '--jobs', '1', '--revision',
'src@' + self.githash('repo_1', 1),
'--deps', 'mac', '--delete_unversioned_trees'],
['deleting'])
tree = self.mangle_git_tree(('repo_1@1', 'src'),
('repo_2@2', 'src/repo2'),
('repo_3@1', 'src/repo2/repo3'),
('repo_4@2', 'src/repo4'))
tree['src/git_hooked2'] = 'git_hooked2'
tree['src/repo2/gclient.args'] = '\n'.join([
'# Generated from \'DEPS\'',
'false_var = false',
'false_str_var = false',
'true_var = true',
'true_str_var = true',
'str_var = "abc"',
'cond_var = false',
])
self.assertTree(tree)
# Test incremental sync: delete-unversioned_trees isn't there.
self.parseGclient(
['sync', '--deps', 'mac', '--jobs', '1'],
['running', 'running'])
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@1', 'src/repo2/repo3'),
('repo_3@2', 'src/repo2/repo_renamed'),
('repo_4@2', 'src/repo4'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
tree['src/repo2/gclient.args'] = '\n'.join([
'# Generated from \'DEPS\'',
'false_var = false',
'false_str_var = false',
'true_var = true',
'true_str_var = true',
'str_var = "abc"',
'cond_var = false',
])
self.assertTree(tree)
def testSyncJsonOutput(self):
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
output_json = os.path.join(self.root_dir, 'output.json')
self.gclient(['sync', '--deps', 'mac', '--output-json', output_json])
with open(output_json) as f:
output_json = json.load(f)
self.maxDiff = None
out = {
'solutions': {
'src/': {
'scm': 'git',
'url': self.git_base + 'repo_1',
'revision': self.githash('repo_1', 2),
'was_processed': True,
},
'src/repo2/': {
'scm': 'git',
'url':
self.git_base + 'repo_2@' + self.githash('repo_2', 1)[:7],
'revision': self.githash('repo_2', 1),
'was_processed': True,
},
'src/repo2/repo_renamed/': {
'scm': 'git',
'url': self.git_base + 'repo_3',
'revision': self.githash('repo_3', 2),
'was_processed': True,
},
'src/should_not_process/': {
'scm': None,
'url': self.git_base + 'repo_4',
'revision': None,
'was_processed': False,
},
},
}
self.assertEqual(out, output_json)
def testSyncIgnoredSolutionName(self):
"""TODO(maruel): This will become an error soon."""
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.parseGclient(
['sync', '--deps', 'mac', '--jobs', '1',
'--revision', 'invalid@' + self.githash('repo_1', 1)],
['running', 'running', 'running'],
'Please fix your script, having invalid --revision flags '
'will soon be considered an error.\n')
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
def testSyncNoSolutionName(self):
if not self.enabled:
return
# When no solution name is provided, gclient uses the first solution listed.
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.parseGclient(
['sync', '--deps', 'mac', '--jobs', '1',
'--revision', self.githash('repo_1', 1)],
['running'])
tree = self.mangle_git_tree(('repo_1@1', 'src'),
('repo_2@2', 'src/repo2'),
('repo_3@1', 'src/repo2/repo3'),
('repo_4@2', 'src/repo4'))
tree['src/repo2/gclient.args'] = '\n'.join([
'# Generated from \'DEPS\'',
'false_var = false',
'false_str_var = false',
'true_var = true',
'true_str_var = true',
'str_var = "abc"',
'cond_var = false',
])
self.assertTree(tree)
def testSyncJobs(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
# Test unversioned checkout.
self.parseGclient(
['sync', '--deps', 'mac', '--jobs', '8'],
['running', 'running', 'running'],
untangle=True)
# TODO(maruel): http://crosbug.com/3582 hooks run even if not matching, must
# add sync parsing to get the list of updated files.
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
# Manually remove git_hooked1 before synching to make sure it's not
# recreated.
os.remove(join(self.root_dir, 'src', 'git_hooked1'))
# Test incremental versioned sync: sync backward.
# Use --jobs 1 otherwise the order is not deterministic.
self.parseGclient(
['sync', '--revision', 'src@' + self.githash('repo_1', 1),
'--deps', 'mac', '--delete_unversioned_trees', '--jobs', '1'],
['deleting'],
untangle=True)
tree = self.mangle_git_tree(('repo_1@1', 'src'),
('repo_2@2', 'src/repo2'),
('repo_3@1', 'src/repo2/repo3'),
('repo_4@2', 'src/repo4'))
tree['src/git_hooked2'] = 'git_hooked2'
tree['src/repo2/gclient.args'] = '\n'.join([
'# Generated from \'DEPS\'',
'false_var = false',
'false_str_var = false',
'true_var = true',
'true_str_var = true',
'str_var = "abc"',
'cond_var = false',
])
self.assertTree(tree)
# Test incremental sync: delete-unversioned_trees isn't there.
self.parseGclient(
['sync', '--deps', 'mac', '--jobs', '8'],
['running', 'running'],
untangle=True)
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@1', 'src/repo2/repo3'),
('repo_3@2', 'src/repo2/repo_renamed'),
('repo_4@2', 'src/repo4'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
tree['src/repo2/gclient.args'] = '\n'.join([
'# Generated from \'DEPS\'',
'false_var = false',
'false_str_var = false',
'true_var = true',
'true_str_var = true',
'str_var = "abc"',
'cond_var = false',
])
self.assertTree(tree)
def testSyncFetch(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_13', '--name', 'src'])
_out, _err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 2)])
self.assertEqual(0, rc)
def testSyncFetchUpdate(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_13', '--name', 'src'])
# Sync to an earlier revision first, one that doesn't refer to
# non-standard refs.
_out, _err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 1)])
self.assertEqual(0, rc)
# Make sure update that pulls a non-standard ref works.
_out, _err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 2)])
self.assertEqual(0, rc)
def testSyncDirect(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_12', '--name', 'src'])
_out, _err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', 'refs/changes/1212'])
self.assertEqual(0, rc)
def testSyncUnmanaged(self):
if not self.enabled:
return
self.gclient([
'config', '--spec',
'solutions=[{"name":"src", "url": "%s", "managed": False}]' % (
self.git_base + 'repo_5')])
self.gclient([
'sync', '--revision', 'src@' + self.githash('repo_5', 2)])
self.gclient([
'sync', '--revision', 'src/repo1@%s' % self.githash('repo_1', 1)])
# src is unmanaged, so gclient shouldn't have updated it. It should've
# stayed synced at @2
tree = self.mangle_git_tree(('repo_5@2', 'src'),
('repo_1@1', 'src/repo1'),
('repo_2@1', 'src/repo2'))
tree['src/git_pre_deps_hooked'] = 'git_pre_deps_hooked'
self.maxDiff = None
self.assertTree(tree)
def testSyncUrl(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient([
'sync', '-v', '-v', '-v',
'--revision', 'src/repo2@%s' % self.githash('repo_2', 1),
'--revision', '%srepo_2@%s' % (self.git_base, self.githash('repo_2', 2))
])
# repo_2 should've been synced to @2 instead of @1, since URLs override
# paths.
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@2', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
def testSyncPatchRef(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient([
'sync', '-v', '-v', '-v',
'--revision', 'src/repo2@%s' % self.githash('repo_2', 1),
'--patch-ref',
'%srepo_2@refs/heads/master:%s' % (
self.git_base, self.githash('repo_2', 2)),
])
# Assert that repo_2 files coincide with revision @2 (the patch ref)
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@2', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
# Assert that HEAD revision of repo_2 is @1 (the base we synced to) since we
# should have done a soft reset.
self.assertEqual(
self.githash('repo_2', 1),
self.gitrevparse(os.path.join(self.root_dir, 'src/repo2')))
def testSyncPatchRefNoHooks(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient([
'sync', '-v', '-v', '-v',
'--revision', 'src/repo2@%s' % self.githash('repo_2', 1),
'--patch-ref',
'%srepo_2@refs/heads/master:%s' % (
self.git_base, self.githash('repo_2', 2)),
'--nohooks',
])
# Assert that repo_2 files coincide with revision @2 (the patch ref)
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@2', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
self.assertTree(tree)
# Assert that HEAD revision of repo_2 is @1 (the base we synced to) since we
# should have done a soft reset.
self.assertEqual(
self.githash('repo_2', 1),
self.gitrevparse(os.path.join(self.root_dir, 'src/repo2')))
def testRunHooks(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
os.remove(join(self.root_dir, 'src', 'git_hooked1'))
os.remove(join(self.root_dir, 'src', 'git_hooked2'))
# runhooks runs all hooks even if not matching by design.
out = self.parseGclient(['runhooks', '--deps', 'mac'],
['running', 'running'])
self.assertEqual(1, len(out[0]))
self.assertEqual(1, len(out[1]))
tree = self.mangle_git_tree(('repo_1@2', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
def testRunHooksCondition(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_7', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
tree = self.mangle_git_tree(('repo_7@1', 'src'))
tree['src/should_run'] = 'should_run'
self.assertTree(tree)
def testPreDepsHooks(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_5', '--name', 'src'])
expectation = [
('running', self.root_dir), # git clone
('running', self.root_dir), # pre-deps hook
]
out = self.parseGclient(['sync', '--deps', 'mac', '--jobs=1',
'--revision', 'src@' + self.githash('repo_5', 2)],
expectation)
self.assertEqual('Cloning into ', out[0][1][:13])
self.assertEqual(2, len(out[1]), out[1])
self.assertEqual('pre-deps hook', out[1][1])
tree = self.mangle_git_tree(('repo_5@2', 'src'),
('repo_1@2', 'src/repo1'),
('repo_2@1', 'src/repo2')
)
tree['src/git_pre_deps_hooked'] = 'git_pre_deps_hooked'
self.assertTree(tree)
os.remove(join(self.root_dir, 'src', 'git_pre_deps_hooked'))
# Pre-DEPS hooks don't run with runhooks.
self.gclient(['runhooks', '--deps', 'mac'])
tree = self.mangle_git_tree(('repo_5@2', 'src'),
('repo_1@2', 'src/repo1'),
('repo_2@1', 'src/repo2')
)
self.assertTree(tree)
# Pre-DEPS hooks run when syncing with --nohooks.
self.gclient(['sync', '--deps', 'mac', '--nohooks',
'--revision', 'src@' + self.githash('repo_5', 2)])
tree = self.mangle_git_tree(('repo_5@2', 'src'),
('repo_1@2', 'src/repo1'),
('repo_2@1', 'src/repo2')
)
tree['src/git_pre_deps_hooked'] = 'git_pre_deps_hooked'
self.assertTree(tree)
os.remove(join(self.root_dir, 'src', 'git_pre_deps_hooked'))
# Pre-DEPS hooks don't run with --noprehooks
self.gclient(['sync', '--deps', 'mac', '--noprehooks',
'--revision', 'src@' + self.githash('repo_5', 2)])
tree = self.mangle_git_tree(('repo_5@2', 'src'),
('repo_1@2', 'src/repo1'),
('repo_2@1', 'src/repo2')
)
self.assertTree(tree)
def testPreDepsHooksError(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_5', '--name', 'src'])
expectated_stdout = [
('running', self.root_dir), # git clone
('running', self.root_dir), # pre-deps hook
('running', self.root_dir), # pre-deps hook (fails)
]
executable = sys.executable
# On Python 3 we always execute hooks with 'python', so we cannot use
# sys.executable.
if sys.version_info.major == 3:
executable = subprocess.check_output(
['python', '-c', 'import sys; print(sys.executable)'])
executable = executable.decode('utf-8').strip()
expected_stderr = ("Error: Command '%s -c import sys; "
"sys.exit(1)' returned non-zero exit status 1 in %s\n"
% (executable, self.root_dir))
stdout, stderr, retcode = self.gclient(['sync', '--deps', 'mac', '--jobs=1',
'--revision',
'src@' + self.githash('repo_5', 3)])
self.assertEqual(stderr, expected_stderr)
self.assertEqual(2, retcode)
self.checkBlock(stdout, expectated_stdout)
def testRevInfo(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo', '--deps', 'mac'])
out = ('src: %(base)srepo_1\n'
'src/repo2: %(base)srepo_2@%(hash2)s\n'
'src/repo2/repo_renamed: %(base)srepo_3\n' %
{
'base': self.git_base,
'hash2': self.githash('repo_2', 1)[:7],
})
self.check((out, '', 0), results)
def testRevInfoActual(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo', '--deps', 'mac', '--actual'])
out = ('src: %(base)srepo_1@%(hash1)s\n'
'src/repo2: %(base)srepo_2@%(hash2)s\n'
'src/repo2/repo_renamed: %(base)srepo_3@%(hash3)s\n' %
{
'base': self.git_base,
'hash1': self.githash('repo_1', 2),
'hash2': self.githash('repo_2', 1),
'hash3': self.githash('repo_3', 2),
})
self.check((out, '', 0), results)
def testRevInfoFilterPath(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo', '--deps', 'mac', '--filter', 'src'])
out = ('src: %(base)srepo_1\n' %
{
'base': self.git_base,
})
self.check((out, '', 0), results)
def testRevInfoFilterURL(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo', '--deps', 'mac',
'--filter', '%srepo_2' % self.git_base])
out = ('src/repo2: %(base)srepo_2@%(hash2)s\n' %
{
'base': self.git_base,
'hash2': self.githash('repo_2', 1)[:7],
})
self.check((out, '', 0), results)
def testRevInfoFilterURLOrPath(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo', '--deps', 'mac', '--filter', 'src',
'--filter', '%srepo_2' % self.git_base])
out = ('src: %(base)srepo_1\n'
'src/repo2: %(base)srepo_2@%(hash2)s\n' %
{
'base': self.git_base,
'hash2': self.githash('repo_2', 1)[:7],
})
self.check((out, '', 0), results)
def testRevInfoJsonOutput(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
output_json = os.path.join(self.root_dir, 'output.json')
self.gclient(['revinfo', '--deps', 'mac', '--output-json', output_json])
with open(output_json) as f:
output_json = json.load(f)
out = {
'src': {
'url': self.git_base + 'repo_1',
'rev': None,
},
'src/repo2': {
'url': self.git_base + 'repo_2',
'rev': self.githash('repo_2', 1)[:7],
},
'src/repo2/repo_renamed': {
'url': self.git_base + 'repo_3',
'rev': None,
},
}
self.assertEqual(out, output_json)
def testRevInfoJsonOutputSnapshot(self):
if not self.enabled:
return
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
output_json = os.path.join(self.root_dir, 'output.json')
self.gclient(['revinfo', '--deps', 'mac', '--snapshot',
'--output-json', output_json])
with open(output_json) as f:
output_json = json.load(f)
out = [{
'solution_url': self.git_base + 'repo_1',
'managed': True,
'name': 'src',
'deps_file': 'DEPS',
'custom_deps': {
'src/repo2': '%srepo_2@%s' % (
self.git_base, self.githash('repo_2', 1)),
'src/repo2/repo_renamed': '%srepo_3@%s' % (
self.git_base, self.githash('repo_3', 2)),
'src/should_not_process': None,
},
}]
self.assertEqual(out, output_json)
def testSetDep(self):
fake_deps = os.path.join(self.root_dir, 'DEPS.fake')
with open(fake_deps, 'w') as f:
f.write('\n'.join([
'vars = { ',
' "foo_var": "foo_val",',
' "foo_rev": "foo_rev",',
'}',
'deps = {',
' "foo": {',
' "url": "url@{foo_rev}",',
' },',
' "bar": "url@bar_rev",',
'}',
]))
results = self.gclient([
'setdep', '-r', 'foo@new_foo', '-r', 'bar@new_bar',
'--var', 'foo_var=new_val', '--deps-file', fake_deps])
with open(fake_deps) as f:
contents = f.read().splitlines()
self.assertEqual('', results[1], results[1])
self.assertEqual(0, results[2])
self.assertEqual([
'vars = { ',
' "foo_var": "new_val",',
' "foo_rev": "new_foo",',
'}',
'deps = {',
' "foo": {',
' "url": "url@{foo_rev}",',
' },',
' "bar": "url@new_bar",',
'}',
], contents)
def testSetDep_BuiltinVariables(self):
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
fake_deps = os.path.join(self.root_dir, 'DEPS.fake')
with open(fake_deps, 'w') as f:
f.write('\n'.join([
'vars = { ',
' "foo_var": "foo_val",',
' "foo_rev": "foo_rev",',
'}',
'deps = {',
' "foo": {',
' "url": "url@{foo_rev}",',
' },',
' "bar": "url@bar_rev",',
'}',
'hooks = [{',
' "name": "uses_builtin_var",',
' "pattern": ".",',
' "action": ["python", "fake.py",',
' "--with-android={checkout_android}"],',
'}]',
]))
results = self.gclient([
'setdep', '-r', 'foo@new_foo', '-r', 'bar@new_bar',
'--var', 'foo_var=new_val', '--deps-file', fake_deps])
with open(fake_deps) as f:
contents = f.read().splitlines()
self.assertEqual('', results[1], results[1])
self.assertEqual(0, results[2])
self.assertEqual([
'vars = { ',
' "foo_var": "new_val",',
' "foo_rev": "new_foo",',
'}',
'deps = {',
' "foo": {',
' "url": "url@{foo_rev}",',
' },',
' "bar": "url@new_bar",',
'}',
'hooks = [{',
' "name": "uses_builtin_var",',
' "pattern": ".",',
' "action": ["python", "fake.py",',
' "--with-android={checkout_android}"],',
'}]',
], contents)
def testGetDep(self):
fake_deps = os.path.join(self.root_dir, 'DEPS.fake')
with open(fake_deps, 'w') as f:
f.write('\n'.join([
'vars = { ',
' "foo_var": "foo_val",',
' "foo_rev": "foo_rev",',
'}',
'deps = {',
' "foo": {',
' "url": "url@{foo_rev}",',
' },',
' "bar": "url@bar_rev",',
'}',
]))
results = self.gclient([
'getdep', '-r', 'foo', '-r', 'bar','--var', 'foo_var',
'--deps-file', fake_deps])
self.assertEqual('', results[1])
self.assertEqual([
'foo_val',
'foo_rev',
'bar_rev',
], results[0].splitlines())
self.assertEqual(0, results[2])
def testGetDep_BuiltinVariables(self):
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
fake_deps = os.path.join(self.root_dir, 'DEPS.fake')
with open(fake_deps, 'w') as f:
f.write('\n'.join([
'vars = { ',
' "foo_var": "foo_val",',
' "foo_rev": "foo_rev",',
'}',
'deps = {',
' "foo": {',
' "url": "url@{foo_rev}",',
' },',
' "bar": "url@bar_rev",',
'}',
'hooks = [{',
' "name": "uses_builtin_var",',
' "pattern": ".",',
' "action": ["python", "fake.py",',
' "--with-android={checkout_android}"],',
'}]',
]))
results = self.gclient([
'getdep', '-r', 'foo', '-r', 'bar','--var', 'foo_var',
'--deps-file', fake_deps])
self.assertEqual('', results[1])
self.assertEqual([
'foo_val',
'foo_rev',
'bar_rev',
], results[0].splitlines())
self.assertEqual(0, results[2])
def testFlatten(self):
if not self.enabled:
return
output_deps = os.path.join(self.root_dir, 'DEPS.flattened')
self.assertFalse(os.path.exists(output_deps))
self.gclient(['config', self.git_base + 'repo_6', '--name', 'src',
# This should be ignored because 'custom_true_var' isn't
# defined in the DEPS.
'--custom-var', 'custom_true_var=True',
# This should override 'true_var=True' from the DEPS.
'--custom-var', 'true_var="False"'])
self.gclient(['sync'])
self.gclient(['flatten', '-v', '-v', '-v', '--output-deps', output_deps])
# Assert we can sync to the flattened DEPS we just wrote.
solutions = [{
"url": self.git_base + 'repo_6',
'name': 'src',
'deps_file': output_deps
}]
results = self.gclient([
'sync',
'--spec=solutions=%s' % solutions
])
self.assertEqual(results[2], 0)
with open(output_deps) as f:
deps_contents = f.read()
self.maxDiff = None # pylint: disable=attribute-defined-outside-init
self.assertEqual([
'gclient_gn_args_file = "src/repo2/gclient.args"',
'gclient_gn_args = [\'false_var\', \'false_str_var\', \'true_var\', '
'\'true_str_var\', \'str_var\', \'cond_var\']',
'allowed_hosts = [',
' "' + self.git_base + '",',
']',
'',
'deps = {',
' # src -> src/repo2 -> foo/bar',
' "foo/bar": {',
' "url": "' + self.git_base + 'repo_3",',
' "condition": \'(repo2_false_var) and (true_str_var)\',',
' },',
'',
' # src',
' "src": {',
' "url": "' + self.git_base + 'repo_6",',
' },',
'',
' # src -> src/mac_repo',
' "src/mac_repo": {',
' "url": "' + self.git_base + 'repo_5",',
' "condition": \'checkout_mac\',',
' },',
'',
' # src -> src/repo8 -> src/recursed_os_repo',
' "src/recursed_os_repo": {',
' "url": "' + self.git_base + 'repo_5",',
' "condition": \'(checkout_linux) or (checkout_mac)\',',
' },',
'',
' # src -> src/repo15',
' "src/repo15": {',
' "url": "' + self.git_base + 'repo_15",',
' },',
'',
' # src -> src/repo16',
' "src/repo16": {',
' "url": "' + self.git_base + 'repo_16",',
' },',
'',
' # src -> src/repo2',
' "src/repo2": {',
' "url": "' + self.git_base + 'repo_2@%s",' % (
self.githash('repo_2', 1)[:7]),
' "condition": \'true_str_var\',',
' },',
'',
' # src -> src/repo4',
' "src/repo4": {',
' "url": "' + self.git_base + 'repo_4",',
' "condition": \'False\',',
' },',
'',
' # src -> src/repo8',
' "src/repo8": {',
' "url": "' + self.git_base + 'repo_8",',
' },',
'',
' # src -> src/unix_repo',
' "src/unix_repo": {',
' "url": "' + self.git_base + 'repo_5",',
' "condition": \'checkout_linux\',',
' },',
'',
' # src -> src/win_repo',
' "src/win_repo": {',
' "url": "' + self.git_base + 'repo_5",',
' "condition": \'checkout_win\',',
' },',
'',
'}',
'',
'hooks = [',
' # src',
' {',
' "pattern": ".",',
' "condition": \'True\',',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "open(\'src/git_hooked1\', \'w\')'
'.write(\'git_hooked1\')",',
' ]',
' },',
'',
' # src',
' {',
' "pattern": "nonexistent",',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "open(\'src/git_hooked2\', \'w\').write(\'git_hooked2\')",',
' ]',
' },',
'',
' # src',
' {',
' "pattern": ".",',
' "condition": \'checkout_mac\',',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "open(\'src/git_hooked_mac\', \'w\').write('
'\'git_hooked_mac\')",',
' ]',
' },',
'',
' # src -> src/repo15',
' {',
' "name": "absolute_cwd",',
' "pattern": ".",',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "pass",',
' ]',
' },',
'',
' # src -> src/repo16',
' {',
' "name": "relative_cwd",',
' "pattern": ".",',
' "cwd": "src/repo16",',
' "action": [',
' "python",',
' "relative.py",',
' ]',
' },',
'',
']',
'',
'vars = {',
' # src',
' "DummyVariable": \'repo\',',
'',
' # src',
' "cond_var": \'false_str_var and true_var\',',
'',
' # src',
' "false_str_var": \'False\',',
'',
' # src',
' "false_var": False,',
'',
' # src',
' "git_base": \'' + self.git_base + '\',',
'',
' # src',
' "hook1_contents": \'git_hooked1\',',
'',
' # src -> src/repo2',
' "repo2_false_var": \'False\',',
'',
' # src',
' "repo5_var": \'/repo_5\',',
'',
' # src',
' "str_var": \'abc\',',
'',
' # src',
' "true_str_var": \'True\',',
'',
' # src [custom_var override]',
' "true_var": \'False\',',
'',
'}',
'',
'# ' + self.git_base + 'repo_15, DEPS',
'# ' + self.git_base + 'repo_16, DEPS',
'# ' + self.git_base + 'repo_2@%s, DEPS' % (
self.githash('repo_2', 1)[:7]),
'# ' + self.git_base + 'repo_6, DEPS',
'# ' + self.git_base + 'repo_8, DEPS',
], deps_contents.splitlines())
def testFlattenPinAllDeps(self):
if not self.enabled:
return
output_deps = os.path.join(self.root_dir, 'DEPS.flattened')
self.assertFalse(os.path.exists(output_deps))
self.gclient(['config', self.git_base + 'repo_6', '--name', 'src'])
self.gclient(['sync', '--process-all-deps'])
self.gclient(['flatten', '-v', '-v', '-v', '--output-deps', output_deps,
'--pin-all-deps'])
with open(output_deps) as f:
deps_contents = f.read()
self.maxDiff = None # pylint: disable=attribute-defined-outside-init
self.assertEqual([
'gclient_gn_args_file = "src/repo2/gclient.args"',
'gclient_gn_args = [\'false_var\', \'false_str_var\', \'true_var\', '
'\'true_str_var\', \'str_var\', \'cond_var\']',
'allowed_hosts = [',
' "' + self.git_base + '",',
']',
'',
'deps = {',
' # src -> src/repo2 -> foo/bar',
' "foo/bar": {',
' "url": "' + self.git_base + 'repo_3@%s",' % (
self.githash('repo_3', 2)),
' "condition": \'(repo2_false_var) and (true_str_var)\',',
' },',
'',
' # src',
' "src": {',
' "url": "' + self.git_base + 'repo_6@%s",' % (
self.githash('repo_6', 1)),
' },',
'',
' # src -> src/mac_repo',
' "src/mac_repo": {',
' "url": "' + self.git_base + 'repo_5@%s",' % (
self.githash('repo_5', 3)),
' "condition": \'checkout_mac\',',
' },',
'',
' # src -> src/repo8 -> src/recursed_os_repo',
' "src/recursed_os_repo": {',
' "url": "' + self.git_base + 'repo_5@%s",' % (
self.githash('repo_5', 3)),
' "condition": \'(checkout_linux) or (checkout_mac)\',',
' },',
'',
' # src -> src/repo15',
' "src/repo15": {',
' "url": "' + self.git_base + 'repo_15@%s",' % (
self.githash('repo_15', 1)),
' },',
'',
' # src -> src/repo16',
' "src/repo16": {',
' "url": "' + self.git_base + 'repo_16@%s",' % (
self.githash('repo_16', 1)),
' },',
'',
' # src -> src/repo2',
' "src/repo2": {',
' "url": "' + self.git_base + 'repo_2@%s",' % (
self.githash('repo_2', 1)),
' "condition": \'true_str_var\',',
' },',
'',
' # src -> src/repo4',
' "src/repo4": {',
' "url": "' + self.git_base + 'repo_4@%s",' % (
self.githash('repo_4', 2)),
' "condition": \'False\',',
' },',
'',
' # src -> src/repo8',
' "src/repo8": {',
' "url": "' + self.git_base + 'repo_8@%s",' % (
self.githash('repo_8', 1)),
' },',
'',
' # src -> src/unix_repo',
' "src/unix_repo": {',
' "url": "' + self.git_base + 'repo_5@%s",' % (
self.githash('repo_5', 3)),
' "condition": \'checkout_linux\',',
' },',
'',
' # src -> src/win_repo',
' "src/win_repo": {',
' "url": "' + self.git_base + 'repo_5@%s",' % (
self.githash('repo_5', 3)),
' "condition": \'checkout_win\',',
' },',
'',
'}',
'',
'hooks = [',
' # src',
' {',
' "pattern": ".",',
' "condition": \'True\',',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "open(\'src/git_hooked1\', \'w\')'
'.write(\'git_hooked1\')",',
' ]',
' },',
'',
' # src',
' {',
' "pattern": "nonexistent",',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "open(\'src/git_hooked2\', \'w\').write(\'git_hooked2\')",',
' ]',
' },',
'',
' # src',
' {',
' "pattern": ".",',
' "condition": \'checkout_mac\',',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "open(\'src/git_hooked_mac\', \'w\').write('
'\'git_hooked_mac\')",',
' ]',
' },',
'',
' # src -> src/repo15',
' {',
' "name": "absolute_cwd",',
' "pattern": ".",',
' "cwd": ".",',
' "action": [',
' "python",',
' "-c",',
' "pass",',
' ]',
' },',
'',
' # src -> src/repo16',
' {',
' "name": "relative_cwd",',
' "pattern": ".",',
' "cwd": "src/repo16",',
' "action": [',
' "python",',
' "relative.py",',
' ]',
' },',
'',
']',
'',
'vars = {',
' # src',
' "DummyVariable": \'repo\',',
'',
' # src',
' "cond_var": \'false_str_var and true_var\',',
'',
' # src',
' "false_str_var": \'False\',',
'',
' # src',
' "false_var": False,',
'',
' # src',
' "git_base": \'' + self.git_base + '\',',
'',
' # src',
' "hook1_contents": \'git_hooked1\',',
'',
' # src -> src/repo2',
' "repo2_false_var": \'False\',',
'',
' # src',
' "repo5_var": \'/repo_5\',',
'',
' # src',
' "str_var": \'abc\',',
'',
' # src',
' "true_str_var": \'True\',',
'',
' # src',
' "true_var": True,',
'',
'}',
'',
'# ' + self.git_base + 'repo_15@%s, DEPS' % (
self.githash('repo_15', 1)),
'# ' + self.git_base + 'repo_16@%s, DEPS' % (
self.githash('repo_16', 1)),
'# ' + self.git_base + 'repo_2@%s, DEPS' % (
self.githash('repo_2', 1)),
'# ' + self.git_base + 'repo_6@%s, DEPS' % (
self.githash('repo_6', 1)),
'# ' + self.git_base + 'repo_8@%s, DEPS' % (
self.githash('repo_8', 1)),
], deps_contents.splitlines())
def testFlattenRecursedeps(self):
if not self.enabled:
return
output_deps = os.path.join(self.root_dir, 'DEPS.flattened')
self.assertFalse(os.path.exists(output_deps))
output_deps_files = os.path.join(self.root_dir, 'DEPS.files')
self.assertFalse(os.path.exists(output_deps_files))
self.gclient(['config', self.git_base + 'repo_10', '--name', 'src'])
self.gclient(['sync', '--process-all-deps'])
self.gclient(['flatten', '-v', '-v', '-v',
'--output-deps', output_deps,
'--output-deps-files', output_deps_files])
with open(output_deps) as f:
deps_contents = f.read()
self.maxDiff = None
self.assertEqual([
'gclient_gn_args_file = "src/repo2/gclient.args"',
"gclient_gn_args = ['str_var']",
'deps = {',
' # src',
' "src": {',
' "url": "' + self.git_base + 'repo_10",',
' },',
'',
' # src -> src/repo9 -> src/repo8 -> src/recursed_os_repo',
' "src/recursed_os_repo": {',
' "url": "' + self.git_base + 'repo_5",',
' "condition": \'(checkout_linux) or (checkout_mac)\',',
' },',
'',
' # src -> src/repo11',
' "src/repo11": {',
' "url": "' + self.git_base + 'repo_11",',
' "condition": \'(checkout_ios) or (checkout_mac)\',',
' },',
'',
' # src -> src/repo11 -> src/repo12',
' "src/repo12": {',
' "url": "' + self.git_base + 'repo_12",',
' "condition": \'(checkout_ios) or (checkout_mac)\',',
' },',
'',
' # src -> src/repo9 -> src/repo4',
' "src/repo4": {',
' "url": "' + self.git_base + 'repo_4",',
' "condition": \'checkout_android\',',
' },',
'',
' # src -> src/repo6',
' "src/repo6": {',
' "url": "' + self.git_base + 'repo_6",',
' },',
'',
' # src -> src/repo9 -> src/repo7',
' "src/repo7": {',
' "url": "' + self.git_base + 'repo_7",',
' },',
'',
' # src -> src/repo9 -> src/repo8',
' "src/repo8": {',
' "url": "' + self.git_base + 'repo_8",',
' },',
'',
' # src -> src/repo9',
' "src/repo9": {',
' "url": "' + self.git_base + 'repo_9",',
' },',
'',
'}',
'',
'vars = {',
' # src -> src/repo9',
' "str_var": \'xyz\',',
'',
'}',
'',
'# ' + self.git_base + 'repo_10, DEPS',
'# ' + self.git_base + 'repo_11, DEPS',
'# ' + self.git_base + 'repo_8, DEPS',
'# ' + self.git_base + 'repo_9, DEPS',
], deps_contents.splitlines())
with open(output_deps_files) as f:
deps_files_contents = json.load(f)
self.assertEqual([
{'url': self.git_base + 'repo_10', 'deps_file': 'DEPS',
'hierarchy': [['src', self.git_base + 'repo_10']]},
{'url': self.git_base + 'repo_11', 'deps_file': 'DEPS',
'hierarchy': [['src', self.git_base + 'repo_10'],
['src/repo11', self.git_base + 'repo_11']]},
{'url': self.git_base + 'repo_8', 'deps_file': 'DEPS',
'hierarchy': [['src', self.git_base + 'repo_10'],
['src/repo9', self.git_base + 'repo_9'],
['src/repo8', self.git_base + 'repo_8']]},
{'url': self.git_base + 'repo_9', 'deps_file': 'DEPS',
'hierarchy': [['src', self.git_base + 'repo_10'],
['src/repo9', self.git_base + 'repo_9']]},
], deps_files_contents)
def testFlattenCipd(self):
if not self.enabled:
return
output_deps = os.path.join(self.root_dir, 'DEPS.flattened')
self.assertFalse(os.path.exists(output_deps))
self.gclient(['config', self.git_base + 'repo_14', '--name', 'src'])
self.gclient(['sync'])
self.gclient(['flatten', '-v', '-v', '-v', '--output-deps', output_deps])
with open(output_deps) as f:
deps_contents = f.read()
self.maxDiff = None # pylint: disable=attribute-defined-outside-init
self.assertEqual([
'deps = {',
' # src',
' "src": {',
' "url": "' + self.git_base + 'repo_14",',
' },',
'',
' # src -> src/another_cipd_dep',
' "src/another_cipd_dep": {',
' "packages": [',
' {',
' "package": "package1",',
' "version": "1.1-cr0",',
' },',
' {',
' "package": "package2",',
' "version": "1.13",',
' },',
' ],',
' "dep_type": "cipd",',
' },',
'',
' # src -> src/cipd_dep',
' "src/cipd_dep": {',
' "packages": [',
' {',
' "package": "package0",',
' "version": "0.1",',
' },',
' ],',
' "dep_type": "cipd",',
' },',
'',
' # src -> src/cipd_dep_with_cipd_variable',
' "src/cipd_dep_with_cipd_variable": {',
' "packages": [',
' {',
' "package": "package3/${{platform}}",',
' "version": "1.2",',
' },',
' ],',
' "dep_type": "cipd",',
' },',
'',
'}',
'',
'# ' + self.git_base + 'repo_14, DEPS',
], deps_contents.splitlines())
class GClientSmokeGITMutates(GClientSmokeBase):
"""testRevertAndStatus mutates the git repo so move it to its own suite."""
def setUp(self):
super(GClientSmokeGITMutates, self).setUp()
self.enabled = self.FAKE_REPOS.set_up_git()
def testRevertAndStatus(self):
if not self.enabled:
return
# Commit new change to repo to make repo_2's hash use a custom_var.
cur_deps = self.FAKE_REPOS.git_hashes['repo_1'][-1][1]['DEPS']
repo_2_hash = self.FAKE_REPOS.git_hashes['repo_2'][1][0][:7]
new_deps = cur_deps.replace('repo_2@%s\'' % repo_2_hash,
'repo_2@\' + Var(\'r2hash\')')
new_deps = 'vars = {\'r2hash\': \'%s\'}\n%s' % (repo_2_hash, new_deps)
self.FAKE_REPOS._commit_git('repo_1', { # pylint: disable=protected-access
'DEPS': new_deps,
'origin': 'git/repo_1@3\n',
})
config_template = (
"""solutions = [{
"name" : "src",
"url" : "%(git_base)srepo_1",
"deps_file" : "DEPS",
"managed" : True,
"custom_vars" : %(custom_vars)s,
}]""")
self.gclient(['config', '--spec', config_template % {
'git_base': self.git_base,
'custom_vars': {}
}])
# Tested in testSync.
self.gclient(['sync', '--deps', 'mac'])
write(join(self.root_dir, 'src', 'repo2', 'hi'), 'Hey!')
out = self.parseGclient(['status', '--deps', 'mac', '--jobs', '1'], [])
# TODO(maruel): http://crosbug.com/3584 It should output the unversioned
# files.
self.assertEqual(0, len(out))
# Revert implies --force implies running hooks without looking at pattern
# matching. For each expected path, 'git reset' and 'git clean' are run, so
# there should be two results for each. The last two results should reflect
# writing git_hooked1 and git_hooked2. There's only one result for the third
# because it is clean and has no output for 'git clean'.
out = self.parseGclient(['revert', '--deps', 'mac', '--jobs', '1'],
['running', 'running'])
self.assertEqual(2, len(out))
tree = self.mangle_git_tree(('repo_1@3', 'src'),
('repo_2@1', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
# Make a new commit object in the origin repo, to force reset to fetch.
self.FAKE_REPOS._commit_git('repo_2', { # pylint: disable=protected-access
'origin': 'git/repo_2@3\n',
})
self.gclient(['config', '--spec', config_template % {
'git_base': self.git_base,
'custom_vars': {'r2hash': self.FAKE_REPOS.git_hashes['repo_2'][-1][0] }
}])
out = self.parseGclient(['revert', '--deps', 'mac', '--jobs', '1'],
['running', 'running'])
self.assertEqual(2, len(out))
tree = self.mangle_git_tree(('repo_1@3', 'src'),
('repo_2@3', 'src/repo2'),
('repo_3@2', 'src/repo2/repo_renamed'))
tree['src/git_hooked1'] = 'git_hooked1'
tree['src/git_hooked2'] = 'git_hooked2'
self.assertTree(tree)
results = self.gclient(['status', '--deps', 'mac', '--jobs', '1'])
out = results[0].splitlines(False)
# TODO(maruel): http://crosbug.com/3584 It should output the unversioned
# files.
self.assertEqual(0, len(out))
def testSyncNoHistory(self):
if not self.enabled:
return
# Create an extra commit in repo_2 and point DEPS to its hash.
cur_deps = self.FAKE_REPOS.git_hashes['repo_1'][-1][1]['DEPS']
repo_2_hash_old = self.FAKE_REPOS.git_hashes['repo_2'][1][0][:7]
self.FAKE_REPOS._commit_git('repo_2', { # pylint: disable=protected-access
'last_file': 'file created in last commit',
})
repo_2_hash_new = self.FAKE_REPOS.git_hashes['repo_2'][-1][0]
new_deps = cur_deps.replace(repo_2_hash_old, repo_2_hash_new)
self.assertNotEqual(new_deps, cur_deps)
self.FAKE_REPOS._commit_git('repo_1', { # pylint: disable=protected-access
'DEPS': new_deps,
'origin': 'git/repo_1@4\n',
})
config_template = (
"""solutions = [{
"name" : "src",
"url" : "%(git_base)srepo_1",
"deps_file" : "DEPS",
"managed" : True,
}]""")
self.gclient(['config', '--spec', config_template % {
'git_base': self.git_base
}])
self.gclient(['sync', '--no-history', '--deps', 'mac'])
repo2_root = join(self.root_dir, 'src', 'repo2')
# Check that repo_2 is actually shallow and its log has only one entry.
rev_lists = subprocess2.check_output(['git', 'rev-list', 'HEAD'],
cwd=repo2_root).decode('utf-8')
self.assertEqual(repo_2_hash_new, rev_lists.strip('\r\n'))
# Check that we have actually checked out the right commit.
self.assertTrue(os.path.exists(join(repo2_root, 'last_file')))
class SkiaDEPSTransitionSmokeTest(GClientSmokeBase):
"""Simulate the behavior of bisect bots as they transition across the Skia
DEPS change."""
FAKE_REPOS_CLASS = fake_repos.FakeRepoSkiaDEPS
def setUp(self):
super(SkiaDEPSTransitionSmokeTest, self).setUp()
self.enabled = self.FAKE_REPOS.set_up_git()
def testSkiaDEPSChangeGit(self):
if not self.enabled:
return
# Create an initial checkout:
# - Single checkout at the root.
# - Multiple checkouts in a shared subdirectory.
self.gclient(['config', '--spec',
'solutions=['
'{"name": "src",'
' "url": "' + self.git_base + 'repo_2",'
'}]'])
checkout_path = os.path.join(self.root_dir, 'src')
skia = os.path.join(checkout_path, 'third_party', 'skia')
skia_gyp = os.path.join(skia, 'gyp')
skia_include = os.path.join(skia, 'include')
skia_src = os.path.join(skia, 'src')
gyp_git_url = self.git_base + 'repo_3'
include_git_url = self.git_base + 'repo_4'
src_git_url = self.git_base + 'repo_5'
skia_git_url = self.FAKE_REPOS.git_base + 'repo_1'
pre_hash = self.githash('repo_2', 1)
post_hash = self.githash('repo_2', 2)
# Initial sync. Verify that we get the expected checkout.
res = self.gclient(['sync', '--deps', 'mac', '--revision',
'src@%s' % pre_hash])
self.assertEqual(res[2], 0, 'Initial sync failed.')
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_gyp), gyp_git_url)
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_include), include_git_url)
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_src), src_git_url)
# Verify that the sync succeeds. Verify that we have the expected merged
# checkout.
res = self.gclient(['sync', '--deps', 'mac', '--revision',
'src@%s' % post_hash])
self.assertEqual(res[2], 0, 'DEPS change sync failed.')
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia), skia_git_url)
# Sync again. Verify that we still have the expected merged checkout.
res = self.gclient(['sync', '--deps', 'mac', '--revision',
'src@%s' % post_hash])
self.assertEqual(res[2], 0, 'Subsequent sync failed.')
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia), skia_git_url)
# Sync back to the original DEPS. Verify that we get the original structure.
res = self.gclient(['sync', '--deps', 'mac', '--revision',
'src@%s' % pre_hash])
self.assertEqual(res[2], 0, 'Reverse sync failed.')
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_gyp), gyp_git_url)
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_include), include_git_url)
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_src), src_git_url)
# Sync again. Verify that we still have the original structure.
res = self.gclient(['sync', '--deps', 'mac', '--revision',
'src@%s' % pre_hash])
self.assertEqual(res[2], 0, 'Subsequent sync #2 failed.')
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_gyp), gyp_git_url)
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_include), include_git_url)
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
skia_src), src_git_url)
class BlinkDEPSTransitionSmokeTest(GClientSmokeBase):
"""Simulate the behavior of bisect bots as they transition across the Blink
DEPS change."""
FAKE_REPOS_CLASS = fake_repos.FakeRepoBlinkDEPS
def setUp(self):
super(BlinkDEPSTransitionSmokeTest, self).setUp()
self.enabled = self.FAKE_REPOS.set_up_git()
self.checkout_path = os.path.join(self.root_dir, 'src')
self.blink = os.path.join(self.checkout_path, 'third_party', 'WebKit')
self.blink_git_url = self.FAKE_REPOS.git_base + 'repo_2'
self.pre_merge_sha = self.githash('repo_1', 1)
self.post_merge_sha = self.githash('repo_1', 2)
def CheckStatusPreMergePoint(self):
self.assertEqual(gclient_scm.GIT.Capture(['config', 'remote.origin.url'],
self.blink), self.blink_git_url)
self.assertTrue(os.path.exists(join(self.blink, '.git')))
self.assertTrue(os.path.exists(join(self.blink, 'OWNERS')))
with open(join(self.blink, 'OWNERS')) as f:
owners_content = f.read()
self.assertEqual('OWNERS-pre', owners_content, 'OWNERS not updated')
self.assertTrue(os.path.exists(join(self.blink, 'Source', 'exists_always')))
self.assertTrue(os.path.exists(
join(self.blink, 'Source', 'exists_before_but_not_after')))
self.assertFalse(os.path.exists(
join(self.blink, 'Source', 'exists_after_but_not_before')))
def CheckStatusPostMergePoint(self):
# Check that the contents still exists
self.assertTrue(os.path.exists(join(self.blink, 'OWNERS')))
with open(join(self.blink, 'OWNERS')) as f:
owners_content = f.read()
self.assertEqual('OWNERS-post', owners_content, 'OWNERS not updated')
self.assertTrue(os.path.exists(join(self.blink, 'Source', 'exists_always')))
# Check that file removed between the branch point are actually deleted.
self.assertTrue(os.path.exists(
join(self.blink, 'Source', 'exists_after_but_not_before')))
self.assertFalse(os.path.exists(
join(self.blink, 'Source', 'exists_before_but_not_after')))
# But not the .git folder
self.assertFalse(os.path.exists(join(self.blink, '.git')))
@unittest.skip('flaky')
def testBlinkDEPSChangeUsingGclient(self):
"""Checks that {src,blink} repos are consistent when syncing going back and
forth using gclient sync src@revision."""
if not self.enabled:
return
self.gclient(['config', '--spec',
'solutions=['
'{"name": "src",'
' "url": "' + self.git_base + 'repo_1",'
'}]'])
# Go back and forth two times.
for _ in range(2):
res = self.gclient(['sync', '--jobs', '1',
'--revision', 'src@%s' % self.pre_merge_sha])
self.assertEqual(res[2], 0, 'DEPS change sync failed.')
self.CheckStatusPreMergePoint()
res = self.gclient(['sync', '--jobs', '1',
'--revision', 'src@%s' % self.post_merge_sha])
self.assertEqual(res[2], 0, 'DEPS change sync failed.')
self.CheckStatusPostMergePoint()
@unittest.skip('flaky')
def testBlinkDEPSChangeUsingGit(self):
"""Like testBlinkDEPSChangeUsingGclient, but move the main project using
directly git and not gclient sync."""
if not self.enabled:
return
self.gclient(['config', '--spec',
'solutions=['
'{"name": "src",'
' "url": "' + self.git_base + 'repo_1",'
' "managed": False,'
'}]'])
# Perform an initial sync to bootstrap the repo.
res = self.gclient(['sync', '--jobs', '1'])
self.assertEqual(res[2], 0, 'Initial gclient sync failed.')
# Go back and forth two times.
for _ in range(2):
subprocess2.check_call(['git', 'checkout', '-q', self.pre_merge_sha],
cwd=self.checkout_path)
res = self.gclient(['sync', '--jobs', '1'])
self.assertEqual(res[2], 0, 'gclient sync failed.')
self.CheckStatusPreMergePoint()
subprocess2.check_call(['git', 'checkout', '-q', self.post_merge_sha],
cwd=self.checkout_path)
res = self.gclient(['sync', '--jobs', '1'])
self.assertEqual(res[2], 0, 'DEPS change sync failed.')
self.CheckStatusPostMergePoint()
@unittest.skip('flaky')
def testBlinkLocalBranchesArePreserved(self):
"""Checks that the state of local git branches are effectively preserved
when going back and forth."""
if not self.enabled:
return
self.gclient(['config', '--spec',
'solutions=['
'{"name": "src",'
' "url": "' + self.git_base + 'repo_1",'
'}]'])
# Initialize to pre-merge point.
self.gclient(['sync', '--revision', 'src@%s' % self.pre_merge_sha])
self.CheckStatusPreMergePoint()
# Create a branch named "foo".
subprocess2.check_call(['git', 'checkout', '-qB', 'foo'],
cwd=self.blink)
# Cross the pre-merge point.
self.gclient(['sync', '--revision', 'src@%s' % self.post_merge_sha])
self.CheckStatusPostMergePoint()
# Go backwards and check that we still have the foo branch.
self.gclient(['sync', '--revision', 'src@%s' % self.pre_merge_sha])
self.CheckStatusPreMergePoint()
subprocess2.check_call(
['git', 'show-ref', '-q', '--verify', 'refs/heads/foo'], cwd=self.blink)
class GClientSmokeCipd(GClientSmokeBase):
def setUp(self):
super(GClientSmokeCipd, self).setUp()
self.enabled = self.FAKE_REPOS.set_up_git()
self.env['PATH'] = (os.path.join(ROOT_DIR, 'testing_support')
+ os.pathsep + self.env['PATH'])
def testSyncCipd(self):
self.gclient(['config', self.git_base + 'repo_14', '--name', 'src'])
out, err, rc = self.gclient(['sync'])
self.assertEqual(0, rc, out + err)
tree = self.mangle_git_tree(('repo_14@1', 'src'))
tree.update({
'_cipd': '\n'.join([
'$ParanoidMode CheckPresence',
'',
'@Subdir src/another_cipd_dep',
'package1 1.1-cr0',
'package2 1.13',
'',
'@Subdir src/cipd_dep',
'package0 0.1',
'',
'@Subdir src/cipd_dep_with_cipd_variable',
'package3/${platform} 1.2',
'',
'',
]),
'src/another_cipd_dep/_cipd': '\n'.join([
'package1 1.1-cr0',
'package2 1.13',
]),
'src/cipd_dep/_cipd': 'package0 0.1',
'src/cipd_dep_with_cipd_variable/_cipd': 'package3/${platform} 1.2',
})
self.assertTree(tree)
def testConvertGitToCipd(self):
self.gclient(['config', self.git_base + 'repo_13', '--name', 'src'])
# repo_13@1 has src/repo12 as a git dependency.
out, err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 1)])
self.assertEqual(0, rc, out + err)
tree = self.mangle_git_tree(('repo_13@1', 'src'),
('repo_12@1', 'src/repo12'))
self.assertTree(tree)
# repo_13@3 has src/repo12 as a cipd dependency.
out, err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 3),
'--delete_unversioned_trees'])
self.assertEqual(0, rc, out + err)
tree = self.mangle_git_tree(('repo_13@3', 'src'))
tree.update({
'_cipd': '\n'.join([
'$ParanoidMode CheckPresence',
'',
'@Subdir src/repo12',
'foo 1.3',
'',
'',
]),
'src/repo12/_cipd': 'foo 1.3',
})
self.assertTree(tree)
def testConvertCipdToGit(self):
self.gclient(['config', self.git_base + 'repo_13', '--name', 'src'])
# repo_13@3 has src/repo12 as a cipd dependency.
out, err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 3),
'--delete_unversioned_trees'])
self.assertEqual(0, rc, out + err)
tree = self.mangle_git_tree(('repo_13@3', 'src'))
tree.update({
'_cipd': '\n'.join([
'$ParanoidMode CheckPresence',
'',
'@Subdir src/repo12',
'foo 1.3',
'',
'',
]),
'src/repo12/_cipd': 'foo 1.3',
})
self.assertTree(tree)
# repo_13@1 has src/repo12 as a git dependency.
out, err, rc = self.gclient(
['sync', '-v', '-v', '-v', '--revision', self.githash('repo_13', 1)])
self.assertEqual(0, rc, out + err)
tree = self.mangle_git_tree(('repo_13@1', 'src'),
('repo_12@1', 'src/repo12'))
tree.update({
'_cipd': '\n'.join([
'$ParanoidMode CheckPresence',
'',
'@Subdir src/repo12',
'foo 1.3',
'',
'',
]),
'src/repo12/_cipd': 'foo 1.3',
})
self.assertTree(tree)
if __name__ == '__main__':
if '-v' in sys.argv:
logging.basicConfig(level=logging.DEBUG)
if '-c' in sys.argv:
COVERAGE = True
sys.argv.remove('-c')
if os.path.exists('.coverage'):
os.remove('.coverage')
os.environ['COVERAGE_FILE'] = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'.coverage')
unittest.main()