You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
depot_tools/tests/gclient_smoketest.py

571 lines
21 KiB
Python

#!/usr/bin/python
# Copyright (c) 2010 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 logging
import os
import pprint
import re
import shutil
import subprocess
import sys
import unittest
from fake_repos import rmtree, write, FakeRepos
join = os.path.join
SHOULD_LEAK = False
UNITTEST_DIR = os.path.abspath(os.path.dirname(__file__))
GCLIENT_PATH = join(os.path.dirname(UNITTEST_DIR), 'gclient')
# all tests outputs goes there.
TRIAL_DIR = join(UNITTEST_DIR, '_trial')
# In case you want to use another machine to create the fake repos, e.g.
# not on Windows.
HOST = '127.0.0.1'
FAKE = None
def read_tree(tree_root):
"""Returns a dict of all the files in a tree."""
tree = {}
for root, dirs, files in os.walk(tree_root):
for d in filter(lambda x: x.startswith('.'), dirs):
dirs.remove(d)
for f in [join(root, f) for f in files if not f.startswith('.')]:
tree[f[len(tree_root) + 1:]] = open(join(root, f), 'rb').read()
return tree
def dict_diff(dict1, dict2):
diff = {}
for k, v in dict1.iteritems():
if k not in dict2:
diff[k] = v
elif v != dict2[k]:
diff[k] = (v, dict2[k])
for k, v in dict2.iteritems():
if k not in dict1:
diff[k] = v
return diff
def mangle_svn_tree(*args):
result = {}
for old_root, new_root, tree in args:
for k, v in tree.iteritems():
if not k.startswith(old_root):
continue
result[join(new_root, k[len(old_root) + 1:])] = v
return result
def mangle_git_tree(*args):
result = {}
for new_root, tree in args:
for k, v in tree.iteritems():
result[join(new_root, k)] = v
return result
class GClientSmokeBase(unittest.TestCase):
# This subversion repository contains a test repository.
ROOT_DIR = join(TRIAL_DIR, 'smoke')
def setUp(self):
# Vaguely inspired by twisted.
# Make sure it doesn't try to auto update when testing!
self.env = os.environ.copy()
self.env['DEPOT_TOOLS_UPDATE'] = '0'
# Remove left overs
self.root_dir = join(self.ROOT_DIR, self.id())
rmtree(self.root_dir)
if not os.path.exists(self.ROOT_DIR):
os.mkdir(self.ROOT_DIR)
os.mkdir(self.root_dir)
self.svn_base = 'svn://%s/svn/' % HOST
self.git_base = 'git://%s/git/' % HOST
def tearDown(self):
if not SHOULD_LEAK:
rmtree(self.root_dir)
def gclient(self, cmd, cwd=None):
if not cwd:
cwd = self.root_dir
process = subprocess.Popen([GCLIENT_PATH] + cmd, cwd=cwd, env=self.env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
shell=sys.platform.startswith('win'))
(stdout, stderr) = process.communicate()
return (stdout, stderr, process.returncode)
def checkString(self, expected, result):
if expected != result:
# Strip the begining
while expected and result and expected[0] == result[0]:
expected = expected[1:]
result = result[1:]
# The exception trace makes it hard to read so dump it too.
if '\n' in result:
print result
self.assertEquals(expected, result)
def check(self, expected, results):
self.checkString(expected[0], results[0])
self.checkString(expected[1], results[1])
self.assertEquals(expected[2], results[2])
def assertTree(self, tree):
actual = read_tree(self.root_dir)
diff = dict_diff(tree, actual)
if diff:
logging.debug('Actual %s\n%s' % (self.root_dir, pprint.pformat(actual)))
logging.debug('Expected\n%s' % pprint.pformat(tree))
logging.debug('Diff\n%s' % pprint.pformat(diff))
self.assertEquals(tree, actual)
class GClientSmoke(GClientSmokeBase):
def testHelp(self):
"""testHelp: make sure no new command was added."""
result = self.gclient(['help'])
self.assertEquals(1197, len(result[0]))
self.assertEquals(0, len(result[1]))
self.assertEquals(0, result[2])
def testUnknown(self):
result = self.gclient(['foo'])
self.assertEquals(1197, len(result[0]))
self.assertEquals(0, len(result[1]))
self.assertEquals(0, result[2])
def testNotConfigured(self):
res = ('', 'Error: client not configured; see \'gclient config\'\n', 1)
self.check(res, self.gclient(['cleanup']))
self.check(res, self.gclient(['diff']))
self.check(res, self.gclient(['export', 'foo']))
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):
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)
self.checkString(expected, open(p, 'rb').read())
test(['config', self.svn_base + 'trunk/src/'],
'solutions = [\n'
' { "name" : "src",\n'
' "url" : "svn://127.0.0.1/svn/trunk/src",\n'
' "custom_deps" : {\n'
' },\n'
' "safesync_url": ""\n'
' },\n]\n')
test(['config', self.git_base + 'repo_1', '--name', 'src'],
'solutions = [\n'
' { "name" : "src",\n'
' "url" : "git://127.0.0.1/git/repo_1",\n'
' "custom_deps" : {\n'
' },\n'
' "safesync_url": ""\n'
' },\n]\n')
test(['config', 'foo', 'faa'],
'solutions = [\n'
' { "name" : "foo",\n'
' "url" : "foo",\n'
' "custom_deps" : {\n'
' },\n'
' "safesync_url": "faa"\n'
' },\n]\n')
test(['config', '--spec', '["blah blah"]'], '["blah blah"]')
os.remove(p)
results = self.gclient(['config', 'foo', 'faa', 'fuu'])
err = ('Usage: gclient.py config [options] [url] [safesync 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')))
class GClientSmokeSVN(GClientSmokeBase):
def testSync(self):
# TODO(maruel): safesync, multiple solutions, invalid@revisions,
# multiple revisions.
self.gclient(['config', self.svn_base + 'trunk/src/'])
# Test unversioned checkout.
results = self.gclient(['sync', '--deps', 'mac'])
logging.debug(results[0])
out = results[0].splitlines(False)
self.assertEquals(17, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[-1]),
(join('trunk', 'third_party', 'foo'), join('src', 'third_party', 'foo'),
FAKE.svn_revs[1]),
(join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]),
)
tree[join('src', 'hooked1')] = 'hooked1'
self.assertTree(tree)
# Manually remove hooked1 before synching to make sure it's not recreated.
os.remove(join(self.root_dir, 'src', 'hooked1'))
# Test incremental versioned sync: sync backward.
results = self.gclient(['sync', '--revision', 'src@1', '--deps', 'mac',
'--delete_unversioned_trees'])
logging.debug(results[0])
out = results[0].splitlines(False)
self.assertEquals(19, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[1]),
(join('trunk', 'third_party', 'foo'), join('src', 'third_party', 'fpp'),
FAKE.svn_revs[2]),
(join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]),
(join('trunk', 'third_party', 'foo'),
join('src', 'third_party', 'prout'),
FAKE.svn_revs[2]),
)
self.assertTree(tree)
# Test incremental sync: delete-unversioned_trees isn't there.
results = self.gclient(['sync', '--deps', 'mac'])
logging.debug(results[0])
out = results[0].splitlines(False)
self.assertEquals(21, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[-1]),
(join('trunk', 'third_party', 'foo'), join('src', 'third_party', 'fpp'),
FAKE.svn_revs[2]),
(join('trunk', 'third_party', 'foo'), join('src', 'third_party', 'foo'),
FAKE.svn_revs[1]),
(join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]),
(join('trunk', 'third_party', 'foo'),
join('src', 'third_party', 'prout'),
FAKE.svn_revs[2]),
)
tree[join('src', 'hooked1')] = 'hooked1'
self.assertTree(tree)
def testRevertAndStatus(self):
self.gclient(['config', self.svn_base + 'trunk/src/'])
# Tested in testSync.
self.gclient(['sync', '--deps', 'mac'])
write(join(self.root_dir, 'src', 'other', 'hi'), 'Hey!')
results = self.gclient(['status'])
out = results[0].splitlines(False)
self.assertEquals(out[0], '')
self.assertTrue(out[1].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[2], '? other')
self.assertEquals(out[3], '? hooked1')
self.assertEquals(out[4], '? third_party/foo')
self.assertEquals(out[5], '')
self.assertTrue(out[6].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[7], '? hi')
self.assertEquals(8, len(out))
self.assertEquals('', results[1])
self.assertEquals(0, results[2])
# Revert implies --force implies running hooks without looking at pattern
# matching.
results = self.gclient(['revert'])
out = results[0].splitlines(False)
self.assertEquals(22, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[-1]),
(join('trunk', 'third_party', 'foo'), join('src', 'third_party', 'foo'),
FAKE.svn_revs[1]),
(join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]),
)
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree)
results = self.gclient(['status'])
out = results[0].splitlines(False)
self.assertEquals(out[0], '')
self.assertTrue(out[1].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[2], '? other')
self.assertEquals(out[3], '? hooked1')
self.assertEquals(out[4], '? hooked2')
self.assertEquals(out[5], '? third_party/foo')
self.assertEquals(6, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
def testRevertAndStatusDepsOs(self):
self.gclient(['config', self.svn_base + 'trunk/src/'])
# Tested in testSync.
self.gclient(['sync', '--deps', 'mac', '--revision', 'src@1'])
write(join(self.root_dir, 'src', 'other', 'hi'), 'Hey!')
results = self.gclient(['status', '--deps', 'mac'])
out = results[0].splitlines(False)
self.assertEquals(out[0], '')
self.assertTrue(out[1].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[2], '? other')
self.assertEquals(out[3], '? third_party/fpp')
self.assertEquals(out[4], '? third_party/prout')
self.assertEquals(out[5], '')
self.assertTrue(out[6].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[7], '? hi')
self.assertEquals(8, len(out))
self.assertEquals('', results[1])
self.assertEquals(0, results[2])
# Revert implies --force implies running hooks without looking at pattern
# matching.
results = self.gclient(['revert', '--deps', 'mac'])
out = results[0].splitlines(False)
self.assertEquals(24, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_svn_tree(
(join('trunk', 'src'), 'src', FAKE.svn_revs[1]),
(join('trunk', 'third_party', 'foo'), join('src', 'third_party', 'fpp'),
FAKE.svn_revs[2]),
(join('trunk', 'other'), join('src', 'other'), FAKE.svn_revs[2]),
(join('trunk', 'third_party', 'prout'),
join('src', 'third_party', 'prout'),
FAKE.svn_revs[2]),
)
self.assertTree(tree)
results = self.gclient(['status', '--deps', 'mac'])
out = results[0].splitlines(False)
self.assertEquals(out[0], '')
self.assertTrue(out[1].startswith('________ running \'svn status\' in \''))
self.assertEquals(out[2], '? other')
self.assertEquals(out[3], '? third_party/fpp')
self.assertEquals(out[4], '? third_party/prout')
self.assertEquals(5, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
def testRunHooks(self):
self.gclient(['config', self.svn_base + 'trunk/src/'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['runhooks'])
out = results[0].splitlines(False)
self.assertEquals(4, len(out))
self.assertEquals(out[0], '')
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked1\', \'w\'\)\.write\(\'hooked1\'\)\' in \'.*',
out[1]))
self.assertEquals(out[2], '')
# runhooks runs all hooks even if not matching by design.
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked2\', \'w\'\)\.write\(\'hooked2\'\)\' in \'.*',
out[3]))
self.checkString('', results[1])
self.assertEquals(0, results[2])
def testRunHooks(self):
self.gclient(['config', self.svn_base + 'trunk/src/'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['runhooks'])
out = results[0].splitlines(False)
self.assertEquals(4, len(out))
self.assertEquals(out[0], '')
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked1\', \'w\'\)\.write\(\'hooked1\'\)\' in \'.*',
out[1]))
self.assertEquals(out[2], '')
# runhooks runs all hooks even if not matching by design.
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked2\', \'w\'\)\.write\(\'hooked2\'\)\' in \'.*',
out[3]))
self.checkString('', results[1])
self.assertEquals(0, results[2])
def testRunHooksDepsOs(self):
self.gclient(['config', self.svn_base + 'trunk/src/'])
self.gclient(['sync', '--deps', 'mac', '--revision', 'src@1'])
results = self.gclient(['runhooks'])
self.check(('', '', 0), results)
def testRevInfo(self):
# TODO(maruel): Test multiple solutions.
self.gclient(['config', self.svn_base + 'trunk/src/'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo'])
out = ('src: %(base)s/src@2;\n'
'src/other: %(base)s/other@2;\n'
'src/third_party/foo: %(base)s/third_party/foo@1\n' %
{ 'base': self.svn_base + 'trunk' })
self.check((out, '', 0), results)
class GClientSmokeGIT(GClientSmokeBase):
def testSync(self):
# TODO(maruel): safesync, multiple solutions, invalid@revisions,
# multiple revisions.
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
# Test unversioned checkout.
results = self.gclient(['sync', '--deps', 'mac'])
out = results[0].splitlines(False)
# TODO(maruel): http://crosbug.com/3582 hooks run even if not matching, must
# add sync parsing to get the list of updated files.
self.assertEquals(13, len(out))
self.assertTrue(results[1].startswith('Switched to a new branch \''))
self.assertEquals(0, results[2])
tree = mangle_git_tree(
('src', FAKE.git_hashes['repo_1'][1][1]),
(join('src', 'repo2'), FAKE.git_hashes['repo_2'][0][1]),
(join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]),
)
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree)
# Manually remove hooked1 before synching to make sure it's not recreated.
os.remove(join(self.root_dir, 'src', 'hooked1'))
# Test incremental versioned sync: sync backward.
results = self.gclient(['sync', '--revision',
'src@' + FAKE.git_hashes['repo_1'][0][0],
'--deps', 'mac', '--delete_unversioned_trees'])
logging.debug(results[0])
out = results[0].splitlines(False)
self.assertEquals(20, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_git_tree(
('src', FAKE.git_hashes['repo_1'][0][1]),
(join('src', 'repo2'), FAKE.git_hashes['repo_2'][1][1]),
(join('src', 'repo2', 'repo3'), FAKE.git_hashes['repo_3'][1][1]),
(join('src', 'repo4'), FAKE.git_hashes['repo_4'][1][1]),
)
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree)
# Test incremental sync: delete-unversioned_trees isn't there.
results = self.gclient(['sync', '--deps', 'mac'])
logging.debug(results[0])
out = results[0].splitlines(False)
self.assertEquals(25, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_git_tree(
('src', FAKE.git_hashes['repo_1'][1][1]),
(join('src', 'repo2'), FAKE.git_hashes['repo_2'][1][1]),
(join('src', 'repo2', 'repo3'), FAKE.git_hashes['repo_3'][1][1]),
(join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]),
(join('src', 'repo4'), FAKE.git_hashes['repo_4'][1][1]),
)
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree)
def testRevertAndStatus(self):
"""TODO(maruel): Remove this line once this test is fixed."""
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
# Tested in testSync.
self.gclient(['sync', '--deps', 'mac'])
write(join(self.root_dir, 'src', 'repo2', 'hi'), 'Hey!')
results = self.gclient(['status'])
out = results[0].splitlines(False)
# TODO(maruel): http://crosbug.com/3584 It should output the unversioned
# files.
self.assertEquals(0, len(out))
# Revert implies --force implies running hooks without looking at pattern
# matching.
results = self.gclient(['revert'])
out = results[0].splitlines(False)
# TODO(maruel): http://crosbug.com/3583 It just runs the hooks right now.
self.assertEquals(7, len(out))
self.checkString('', results[1])
self.assertEquals(0, results[2])
tree = mangle_git_tree(
('src', FAKE.git_hashes['repo_1'][1][1]),
(join('src', 'repo2'), FAKE.git_hashes['repo_2'][0][1]),
(join('src', 'repo2', 'repo_renamed'), FAKE.git_hashes['repo_3'][1][1]),
)
# TODO(maruel): http://crosbug.com/3583 This file should have been removed.
tree[join('src', 'repo2', 'hi')] = 'Hey!'
tree[join('src', 'hooked1')] = 'hooked1'
tree[join('src', 'hooked2')] = 'hooked2'
self.assertTree(tree)
results = self.gclient(['status'])
out = results[0].splitlines(False)
# TODO(maruel): http://crosbug.com/3584 It should output the unversioned
# files.
self.assertEquals(0, len(out))
def testRunHooks(self):
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['runhooks'])
logging.debug(results[0])
out = results[0].splitlines(False)
self.assertEquals(4, len(out))
self.assertEquals(out[0], '')
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked1\', \'w\'\)\.write\(\'hooked1\'\)\' in \'.*',
out[1]))
self.assertEquals(out[2], '')
# runhooks runs all hooks even if not matching by design.
self.assertTrue(re.match(r'^________ running \'.*?python -c '
r'open\(\'src/hooked2\', \'w\'\)\.write\(\'hooked2\'\)\' in \'.*',
out[3]))
self.checkString('', results[1])
self.assertEquals(0, results[2])
def testRevInfo(self):
# TODO(maruel): Test multiple solutions.
self.gclient(['config', self.git_base + 'repo_1', '--name', 'src'])
self.gclient(['sync', '--deps', 'mac'])
results = self.gclient(['revinfo'])
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': FAKE.git_hashes['repo_1'][1][0],
'hash2': FAKE.git_hashes['repo_2'][0][0],
'hash3': FAKE.git_hashes['repo_3'][1][0],
})
self.check((out, '', 0), results)
if __name__ == '__main__':
if '-v' in sys.argv:
logging.basicConfig(level=logging.DEBUG)
if '-l' in sys.argv:
SHOULD_LEAK = True
sys.argv.remove('-l')
FAKE = FakeRepos(TRIAL_DIR, SHOULD_LEAK, True)
try:
FAKE.setUp()
unittest.main()
finally:
FAKE.tearDown()