diff --git a/tools_webrtc/mb/mb.py b/tools_webrtc/mb/mb.py index 913409500a..4aff74621f 100755 --- a/tools_webrtc/mb/mb.py +++ b/tools_webrtc/mb/mb.py @@ -323,11 +323,14 @@ class MetaBuildWrapper(object): return ret if self.args.swarmed: - return self._RunUnderSwarming(build_dir, target) + cmd, _ = self.GetSwarmingCommand(self.args.target[0], vals) + return self._RunUnderSwarming(build_dir, target, cmd) else: return self._RunLocallyIsolated(build_dir, target) - def _RunUnderSwarming(self, build_dir, target): + def _RunUnderSwarming(self, build_dir, target, isolate_cmd): + cas_instance = 'chromium-swarm' + swarming_server = 'chromium-swarm.appspot.com' # TODO(dpranke): Look up the information for the target in # the //testing/buildbot.json file, if possible, so that we # can determine the isolate target, command line, and additional @@ -336,7 +339,7 @@ class MetaBuildWrapper(object): # TODO(dpranke): Also, add support for sharding and merging results. dimensions = [] for k, v in self.args.dimensions: - dimensions += ['-d', k, v] + dimensions += ['-d', '%s=%s' % (k, v)] archive_json_path = self.ToSrcRelPath( '%s/%s.archive.json' % (build_dir, target)) @@ -345,13 +348,29 @@ class MetaBuildWrapper(object): 'archive', '-i', self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)), - '-s', - self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)), - '-I', 'isolateserver.appspot.com', - '-dump-json', archive_json_path, - ] - ret, _, _ = self.Run(cmd, force_verbose=False) + '-cas-instance', + cas_instance, + '-dump-json', + archive_json_path, + ] + + # Talking to the isolateserver may fail because we're not logged in. + # We trap the command explicitly and rewrite the error output so that + # the error message is actually correct for a Chromium check out. + self.PrintCmd(cmd, env=None) + ret, out, err = self.Run(cmd, force_verbose=False) if ret: + self.Print(' -> returned %d' % ret) + if out: + self.Print(out, end='') + if err: + # The swarming client will return an exit code of 2 (via + # argparse.ArgumentParser.error()) and print a message to indicate + # that auth failed, so we have to parse the message to check. + if (ret == 2 and 'Please login to' in err): + err = err.replace(' auth.py', ' tools/swarming_client/auth.py') + self.Print(err, end='', file=sys.stderr) + return ret try: @@ -361,7 +380,7 @@ class MetaBuildWrapper(object): 'Failed to read JSON file "%s"' % archive_json_path, file=sys.stderr) return 1 try: - isolated_hash = archive_hashes[target] + cas_digest = archive_hashes[target] except Exception: self.Print( 'Cannot find hash for "%s" in "%s", file content: %s' % @@ -369,16 +388,44 @@ class MetaBuildWrapper(object): file=sys.stderr) return 1 + try: + json_dir = self.TempDir() + json_file = self.PathJoin(json_dir, 'task.json') + + cmd = [ + self.PathJoin('tools', 'luci-go', 'swarming'), + 'trigger', + '-digest', + cas_digest, + '-server', + swarming_server, + '-tag=purpose:user-debug-mb', + '-relative-cwd', + self.ToSrcRelPath(build_dir), + '-dump-json', + json_file, + ] + dimensions + ['--'] + list(isolate_cmd) + + if self.args.extra_args: + cmd += ['--'] + self.args.extra_args + self.Print('') + ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False) + if ret: + return ret + task_json = self.ReadFile(json_file) + task_id = json.loads(task_json)["tasks"][0]['task_id'] + finally: + if json_dir: + self.RemoveDirectory(json_dir) + cmd = [ - self.executable, - self.PathJoin('tools', 'swarming_client', 'swarming.py'), - 'run', - '-s', isolated_hash, - '-I', 'isolateserver.appspot.com', - '-S', 'chromium-swarm.appspot.com', - ] + dimensions - if self.args.extra_args: - cmd += ['--'] + self.args.extra_args + self.PathJoin('tools', 'luci-go', 'swarming'), + 'collect', + '-server', + swarming_server, + '-task-output-stdout=console', + task_id, + ] ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False) return ret @@ -683,7 +730,7 @@ class MetaBuildWrapper(object): raise MBErr('did not generate any of %s' % ', '.join(runtime_deps_targets)) - command, extra_files = self.GetIsolateCommand(target, vals) + command, extra_files = self.GetSwarmingCommand(target, vals) runtime_deps = self.ReadFile(runtime_deps_path).splitlines() @@ -701,7 +748,7 @@ class MetaBuildWrapper(object): label = labels[0] build_dir = self.args.path[0] - command, extra_files = self.GetIsolateCommand(target, vals) + command, extra_files = self.GetSwarmingCommand(target, vals) cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps') ret, out, _ = self.Call(cmd) @@ -824,7 +871,7 @@ class MetaBuildWrapper(object): gn_args = ('import("%s")\n' % vals['args_file']) + gn_args return gn_args - def GetIsolateCommand(self, target, vals): + def GetSwarmingCommand(self, target, vals): isolate_map = self.ReadIsolateMap() test_type = isolate_map[target]['type'] @@ -1188,6 +1235,10 @@ class MetaBuildWrapper(object): else: shutil.rmtree(abs_path, ignore_errors=True) + def TempDir(self): + # This function largely exists so it can be overriden for testing. + return tempfile.mkdtemp(prefix='mb_') + def TempFile(self, mode='w'): # This function largely exists so it can be overriden for testing. return tempfile.NamedTemporaryFile(mode=mode, delete=False) diff --git a/tools_webrtc/mb/mb_unittest.py b/tools_webrtc/mb/mb_unittest.py index eb11d092f8..fc359d9995 100755 --- a/tools_webrtc/mb/mb_unittest.py +++ b/tools_webrtc/mb/mb_unittest.py @@ -13,7 +13,9 @@ import ast import json import StringIO import os +import re import sys +import tempfile import unittest import mb @@ -32,6 +34,7 @@ class FakeMBW(mb.MetaBuildWrapper): self.platform = 'win32' self.executable = 'c:\\python\\python.exe' self.sep = '\\' + self.cwd = 'c:\\fake_src\\out\\Default' else: self.src_dir = '/fake_src' self.default_config = '/fake_src/tools_webrtc/mb/mb_config.pyl' @@ -39,8 +42,10 @@ class FakeMBW(mb.MetaBuildWrapper): self.executable = '/usr/bin/python' self.platform = 'linux2' self.sep = '/' + self.cwd = '/fake_src/out/Default' self.files = {} + self.dirs = set() self.calls = [] self.cmds = [] self.cross_compile = None @@ -52,21 +57,24 @@ class FakeMBW(mb.MetaBuildWrapper): return '$HOME/%s' % path def Exists(self, path): - return self.files.get(path) is not None + abs_path = self._AbsPath(path) + return (self.files.get(abs_path) is not None or abs_path in self.dirs) def MaybeMakeDirectory(self, path): - self.files[path] = True + abpath = self._AbsPath(path) + self.dirs.add(abpath) def PathJoin(self, *comps): return self.sep.join(comps) def ReadFile(self, path): - return self.files[path] + return self.files[self._AbsPath(path)] def WriteFile(self, path, contents, force_verbose=False): if self.args.dryrun or self.args.verbose or force_verbose: self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path)) - self.files[path] = contents + abpath = self._AbsPath(path) + self.files[abpath] = contents def Call(self, cmd, env=None, buffer_output=True): self.calls.append(cmd) @@ -83,18 +91,34 @@ class FakeMBW(mb.MetaBuildWrapper): else: self.out += sep.join(args) + end + def TempDir(self): + tmp_dir = os.path.join(tempfile.gettempdir(), 'mb_test') + self.dirs.add(tmp_dir) + return tmp_dir + def TempFile(self, mode='w'): return FakeFile(self.files) def RemoveFile(self, path): - del self.files[path] + abpath = self._AbsPath(path) + self.files[abpath] = None def RemoveDirectory(self, path): - self.rmdirs.append(path) - files_to_delete = [f for f in self.files if f.startswith(path)] + abpath = self._AbsPath(path) + self.rmdirs.append(abpath) + files_to_delete = [f for f in self.files if f.startswith(abpath)] for f in files_to_delete: self.files[f] = None + def _AbsPath(self, path): + if not ((self.platform == 'win32' and path.startswith('c:')) or + (self.platform != 'win32' and path.startswith('/'))): + path = self.PathJoin(self.cwd, path) + if self.sep == '\\': + return re.sub(r'\\+', r'\\', path) + else: + return re.sub('/+', '/', path) + class FakeFile(object): def __init__(self, files): @@ -176,13 +200,20 @@ class UnitTest(unittest.TestCase): mbw.files[path] = contents return mbw - def check(self, args, mbw=None, files=None, out=None, err=None, ret=None): + def check(self, args, mbw=None, files=None, out=None, err=None, ret=None, + env=None): if not mbw: mbw = self.fake_mbw(files) - actual_ret = mbw.Main(args) - - self.assertEqual(actual_ret, ret) + try: + prev_env = os.environ.copy() + os.environ = env if env else prev_env + actual_ret = mbw.Main(args) + finally: + os.environ = prev_env + self.assertEqual( + actual_ret, ret, + "ret: %s, out: %s, err: %s" % (actual_ret, mbw.out, mbw.err)) if out is not None: self.assertEqual(mbw.out, out) if err is not None: @@ -564,8 +595,8 @@ class UnitTest(unittest.TestCase): def test_gen_windowed_test_launcher_win(self): files = { - '/tmp/swarming_targets': 'unittests\n', - '/fake_src/testing/buildbot/gn_isolate_map.pyl': ( + 'c:\\fake_src\\out\\Default\\tmp\\swarming_targets': 'unittests\n', + 'c:\\fake_src\\testing\\buildbot\\gn_isolate_map.pyl': ( "{'unittests': {" " 'label': '//somewhere:unittests'," " 'type': 'windowed_test_launcher'," @@ -579,9 +610,10 @@ class UnitTest(unittest.TestCase): mbw = self.fake_mbw(files=files, win32=True) self.check(['gen', '-c', 'debug_goma', - '--swarming-targets-file', '/tmp/swarming_targets', + '--swarming-targets-file', + 'c:\\fake_src\\out\\Default\\tmp\\swarming_targets', '--isolate-map-file', - '/fake_src/testing/buildbot/gn_isolate_map.pyl', + 'c:\\fake_src\\testing\\buildbot\\gn_isolate_map.pyl', '//out/Default'], mbw=mbw, ret=0) isolate_file = mbw.files['c:\\fake_src\\out\\Default\\unittests.isolate'] @@ -750,23 +782,40 @@ class UnitTest(unittest.TestCase): def test_run_swarmed(self): files = { - '/fake_src/testing/buildbot/gn_isolate_map.pyl': ( - "{'base_unittests': {" - " 'label': '//base:base_unittests'," - " 'type': 'raw'," - " 'args': []," - "}}\n" - ), - '/fake_src/out/Default/base_unittests.runtime_deps': ( - "base_unittests\n" - ), - 'out/Default/base_unittests.archive.json': ( - "{\"base_unittests\":\"fake_hash\"}"), + '/fake_src/testing/buildbot/gn_isolate_map.pyl': + ("{'base_unittests': {" + " 'label': '//base:base_unittests'," + " 'type': 'console_test_launcher'," + "}}\n"), + '/fake_src/out/Default/base_unittests.runtime_deps': + ("base_unittests\n"), + '/fake_src/out/Default/base_unittests.archive.json': + ("{\"base_unittests\":\"fake_hash\"}"), + '/fake_src/third_party/depot_tools/cipd_manifest.txt': + ("# vpython\n" + "/some/vpython/pkg git_revision:deadbeef\n"), } + task_json = json.dumps({'tasks': [{'task_id': '00000'}]}) + collect_json = json.dumps({'00000': {'results': {}}}) mbw = self.fake_mbw(files=files) + mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json + mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json + original_impl = mbw.ToSrcRelPath + + def to_src_rel_path_stub(path): + if path.endswith('base_unittests.archive.json'): + return 'base_unittests.archive.json' + return original_impl(path) + + mbw.ToSrcRelPath = to_src_rel_path_stub + self.check(['run', '-s', '-c', 'debug_goma', '//out/Default', 'base_unittests'], mbw=mbw, ret=0) + mbw = self.fake_mbw(files=files) + mbw.files[mbw.PathJoin(mbw.TempDir(), 'task.json')] = task_json + mbw.files[mbw.PathJoin(mbw.TempDir(), 'collect_output.json')] = collect_json + mbw.ToSrcRelPath = to_src_rel_path_stub self.check(['run', '-s', '-c', 'debug_goma', '-d', 'os', 'Win7', '//out/Default', 'base_unittests'], mbw=mbw, ret=0)