blob: 10b703620a51b5aa24a0545a91e01815f2dd6e35 [file] [log] [blame]
#!/usr/bin/env vpython
# Copyright 2015 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.
"""Tests that the tools/build annotated_run wrapper actually runs."""
import collections
import json
import logging
import os
import StringIO
import subprocess
import sys
import tempfile
import unittest
import mock
import test_env # pylint: disable=relative-import
from common import annotator
from common import env
from slave import annotated_run
from slave import logdog_bootstrap
from slave import remote_run
from slave import robust_tempdir
from slave import update_scripts
from slave.unittests.utils import FakeBuildRootTestCase
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MockOptions = collections.namedtuple('MockOptions',
('dry_run', 'logdog_disable'))
class AnnotatedRunTest(FakeBuildRootTestCase):
def test_example(self):
build_properties = {
'mastername': 'tryserver.chromium.linux',
'buildername': 'builder',
'buildnumber': 1,
'slavename': 'bot42-m1',
'recipe': 'annotated_run_test',
'true_prop': True,
'num_prop': 123,
'string_prop': '321',
'dict_prop': {'foo': 'bar'},
}
script_path = os.path.join(BASE_DIR, 'annotated_run.py')
exit_code = subprocess.call([
'python', script_path,
'--build-properties=%s' % json.dumps(build_properties)],
cwd=self.fake_build_root,
env=self.get_test_env(),
)
self.assertEqual(exit_code, 0)
def test_passthrough(self):
build_properties = {
'mastername': 'tryserver.chromium.linux',
'buildername': 'builder',
'buildnumber': 1,
'slavename': 'bot42-m1',
'recipe': 'annotated_run_test',
'true_prop': True,
'num_prop': 123,
'string_prop': '321',
'dict_prop': {'foo': 'bar'},
}
subprocess_env = self.get_test_env()
subprocess_env['BUILDBOT_SLAVENAME'] = 'tools.build.test'
script_path = os.path.join(BASE_DIR, 'annotated_run.py')
exit_code = subprocess.call([
'python', script_path,
'--build-properties=%s' % json.dumps(build_properties),
'--remote-run-passthrough',
],
cwd=self.fake_build_root,
env=subprocess_env,
)
self.assertEqual(exit_code, 0)
class AnnotatedRunExecTest(unittest.TestCase):
def setUp(self):
logging.basicConfig(level=logging.ERROR+1)
self._orig_env = os.environ.copy()
self.maxDiff = None
self._patchers = []
map(self._patch, (
mock.patch('slave.annotated_run._run_command'),
mock.patch('slave.annotated_run._build_dir'),
mock.patch('slave.annotated_run._builder_dir'),
mock.patch('os.path.exists'),
))
# Mock build and builder directories.
annotated_run._build_dir.return_value = '/home/user/builder/build'
annotated_run._builder_dir.return_value = '/home/user/builder'
self.rt = robust_tempdir.RobustTempdir(prefix='annotated_run_test')
self.stream_output = StringIO.StringIO()
self.stream = annotator.StructuredAnnotationStream(
stream=self.stream_output)
self.basedir = self.rt.tempdir()
self.tdir = self.rt.tempdir()
self.opts = MockOptions(
dry_run=False,
logdog_disable=False)
self.properties = {
'slavename': 'bot42-m1',
'recipe': 'example/recipe',
'mastername': 'tryserver.chromium.linux',
'buildername': 'builder',
'buildnumber': 1,
}
self.rpy_path = os.path.join(env.Build, 'scripts', 'slave', 'recipes.py')
self.recipe_args = [
mock.ANY, '-u', self.rpy_path, '--verbose', 'run',
'--workdir=/home/user/builder/build',
'--properties-file=%s' % (self._tp('recipe_properties.json'),),
'--output-result-json', self._tp('recipe_result.json'),
'example/recipe']
# Use public recipes.py path.
os.path.exists.return_value = False
def tearDown(self):
os.environ = self._orig_env
self.rt.close()
for p in reversed(self._patchers):
p.stop()
def _bp(self, *p):
return os.path.join(*((self.basedir,) + p))
def _tp(self, *p):
return os.path.join(*((self.tdir,) + p))
def _patch(self, patcher):
self._patchers.append(patcher)
patcher.start()
return patcher
@staticmethod
def _default_namedtuple(typ, default=None):
return typ(**{f: default for f in typ._fields})
def _assertRecipeProperties(self, value):
# Double-translate "value", since JSON converts strings to unicode.
value = json.loads(json.dumps(value))
with open(self._tp('recipe_properties.json')) as fd:
self.assertEqual(json.load(fd), value)
def _writeRecipeResult(self, v):
with open(self._tp('recipe_result.json'), 'w') as fd:
json.dump(v, fd)
@mock.patch('slave.update_scripts._run_command')
@mock.patch('slave.annotated_run.main')
@mock.patch('sys.platform', return_value='win')
@mock.patch('tempfile.mkstemp', side_effect=Exception('failure'))
def test_update_scripts_must_run(self, _tempfile_mkstemp, _sys_platform,
main, update_scripts_run_command):
update_scripts_run_command.return_value = (0, "")
annotated_run._run_command.return_value = (0, "")
main.side_effect = Exception('Test error!')
annotated_run.shell_main(['annotated_run.py', 'foo'])
gclient_path = os.path.join(env.Build, os.pardir, 'depot_tools',
'gclient.bat')
annotated_run._run_command.assert_has_calls([
mock.call([sys.executable, 'annotated_run.py', 'foo']),
])
update_scripts_run_command.assert_has_calls([
mock.call([gclient_path, 'sync',
'--force', '--delete_unversioned_trees',
'--break_repo_locks', '--verbose', '--jobs=2',
'--disable-syntax-validation'],
cwd=env.Build),
])
self.assertFalse(main.called)
def test_exec_successful(self):
annotated_run._run_command.return_value = (0, '')
self._writeRecipeResult({})
rv = annotated_run._exec_recipe(
self.rt, self.opts, self.stream, self.basedir, self.tdir,
self.properties)
self.assertEqual(rv, 0)
self._assertRecipeProperties(self.properties)
annotated_run._run_command.assert_called_once_with(self.recipe_args,
dry_run=False)
@mock.patch('slave.logdog_bootstrap.bootstrap')
@mock.patch('slave.logdog_bootstrap.BootstrapState.get_result')
def test_exec_with_logdog_bootstrap(self, bs_result, bootstrap):
cfg = self._default_namedtuple(logdog_bootstrap.Config)._replace(
params=self._default_namedtuple(logdog_bootstrap.Params)._replace(
project="project",
),
prefix="prefix",
host="example.com",
)
bootstrap.return_value = logdog_bootstrap.BootstrapState(
cfg, ['logdog_bootstrap'] + self.recipe_args, '/path/to/result.json')
bootstrap.return_value.get_result.return_value = 13
annotated_run._run_command.return_value = (13, '')
self._writeRecipeResult({})
rv = annotated_run._exec_recipe(
self.rt, self.opts, self.stream, self.basedir, self.tdir,
self.properties)
self.assertEqual(rv, 13)
annotated_run._run_command.assert_called_once_with(
['logdog_bootstrap'] + self.recipe_args, dry_run=False)
self._assertRecipeProperties(self.properties)
self.assertEqual(
[l for l in self.stream_output.getvalue().splitlines() if l],
[
'@@@SEED_STEP LogDog Bootstrap@@@',
'@@@STEP_CURSOR LogDog Bootstrap@@@',
'@@@STEP_STARTED@@@',
'@@@SET_BUILD_PROPERTY@logdog_project@"project"@@@',
'@@@SET_BUILD_PROPERTY@logdog_prefix@"prefix"@@@',
('@@@SET_BUILD_PROPERTY@log_location@'
'"logdog://example.com/project/prefix/+/recipes/annotations"@@@'),
'@@@STEP_CURSOR LogDog Bootstrap@@@',
'@@@STEP_CLOSED@@@',
]
)
@mock.patch('slave.logdog_bootstrap.bootstrap')
@mock.patch('slave.logdog_bootstrap.BootstrapState.get_result')
def test_exec_with_logdog_bootstrap(self, bs_result, bootstrap):
cfg = self._default_namedtuple(logdog_bootstrap.Config)._replace(
params=self._default_namedtuple(logdog_bootstrap.Params)._replace(
project="project",
),
prefix="prefix",
host="example.com",
)
bootstrap.return_value = logdog_bootstrap.BootstrapState(
cfg, ['logdog_bootstrap'] + self.recipe_args, '/path/to/result.json')
bootstrap.return_value.get_result.return_value = 13
annotated_run._run_command.return_value = (13, '')
self._writeRecipeResult({})
rv = annotated_run._exec_recipe(
self.rt, self.opts, self.stream, self.basedir, self.tdir,
self.properties)
self.assertEqual(rv, 13)
annotated_run._run_command.assert_called_once_with(
['logdog_bootstrap'] + self.recipe_args, dry_run=False)
self._assertRecipeProperties(self.properties)
self.assertEqual(
[l for l in self.stream_output.getvalue().splitlines() if l],
[
'@@@SEED_STEP LogDog Bootstrap@@@',
'@@@STEP_CURSOR LogDog Bootstrap@@@',
'@@@STEP_STARTED@@@',
'@@@SET_BUILD_PROPERTY@logdog_project@"project"@@@',
'@@@SET_BUILD_PROPERTY@logdog_prefix@"prefix"@@@',
('@@@SET_BUILD_PROPERTY@log_location@'
'"logdog://example.com/project/prefix/+/recipes/annotations"@@@'),
'@@@STEP_CURSOR LogDog Bootstrap@@@',
'@@@STEP_CLOSED@@@',
]
)
@mock.patch('slave.logdog_bootstrap.bootstrap')
def test_exec_with_logdog_bootstrap_fail_raises(self, bootstrap):
bootstrap.side_effect = logdog_bootstrap.BootstrapError('Bootstrap failed')
with self.assertRaises(logdog_bootstrap.BootstrapError):
_ = annotated_run._exec_recipe(
self.rt, self.opts, self.stream, self.basedir, self.tdir,
self.properties)
@mock.patch('slave.logdog_bootstrap.bootstrap')
def test_exec_with_result_proto(self, bootstrap):
bootstrap.side_effect = logdog_bootstrap.NotBootstrapped()
annotated_run._run_command.return_value = (13, '')
self._writeRecipeResult({})
rv = annotated_run._exec_recipe(
self.rt, self.opts, self.stream, self.basedir, self.tdir,
self.properties)
self.assertEqual(rv, 13)
@mock.patch('slave.logdog_bootstrap.bootstrap')
def test_exec_with_result_proto_fail(self, bootstrap):
bootstrap.side_effect = logdog_bootstrap.NotBootstrapped()
annotated_run._run_command.return_value = (13, '')
self._writeRecipeResult({
'failure': {},
})
rv = annotated_run._exec_recipe(
self.rt, self.opts, self.stream, self.basedir, self.tdir,
self.properties)
self.assertEqual(rv, 255)
@mock.patch('slave.logdog_bootstrap.bootstrap')
def test_exec_with_result_proto_step_fail(self, bootstrap):
bootstrap.side_effect = logdog_bootstrap.NotBootstrapped()
annotated_run._run_command.return_value = (13, '')
self._writeRecipeResult({
'failure': {
'step_failure': True,
},
})
rv = annotated_run._exec_recipe(
self.rt, self.opts, self.stream, self.basedir, self.tdir,
self.properties)
self.assertEqual(rv, 13)
@mock.patch.dict('slave.annotated_run._REMOTE_RUN_PASSTHROUGH', {
'all': annotated_run._REMOTE_RUN_PASSTHROUGH_ALL,
'some': [
'buildername',
],
})
def test_is_remote_run_passthrough(self):
for mastername, buildername in (
('all', 'anything'),
('some', 'buildername'),
):
props = {'mastername': mastername, 'buildername': buildername}
self.assertTrue(annotated_run._is_remote_run_passthrough(props),
'(%(mastername)s, %(buildername)s) should be passthrough, '
'but was not' % props)
for mastername, buildername in (
('some', 'not_included'),
('nonexist', 'any'),
):
props = {'mastername': mastername, 'buildername': buildername}
self.assertFalse(annotated_run._is_remote_run_passthrough(props),
'(%(mastername)s, %(buildername)s) should not be passthrough, '
'but was' % props)
if __name__ == '__main__':
unittest.main()