Fix FullScreen crash in Webapp
[platform/framework/web/chromium-efl.git] / tools / run-swarmed.py
index ce0c36e..cdb644b 100755 (executable)
@@ -1,6 +1,6 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
-# Copyright 2017 The Chromium Authors. All rights reserved.
+# Copyright 2017 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -20,7 +20,7 @@ results/*` to find the tests that failed or otherwise process the log files.
 See //docs/workflow/debugging-with-swarming.md for more details.
 """
 
-from __future__ import print_function
+
 
 import argparse
 import hashlib
@@ -30,7 +30,14 @@ import os
 import shutil
 import subprocess
 import sys
+import traceback
+
+CHROMIUM_ROOT = os.path.join(os.path.dirname(__file__), os.pardir)
+BUILD_DIR = os.path.join(CHROMIUM_ROOT, 'build')
 
+if BUILD_DIR not in sys.path:
+  sys.path.insert(0, BUILD_DIR)
+import gn_helpers
 
 INTERNAL_ERROR_EXIT_CODE = -1000
 
@@ -41,78 +48,129 @@ def _Spawn(args):
   """Triggers a swarming job. The arguments passed are:
   - The index of the job;
   - The command line arguments object;
-  - The hash of the isolate job used to trigger.
+  - The digest of test files.
 
   The return value is passed to a collect-style map() and consists of:
   - The index of the job;
   - The json file created by triggering and used to collect results;
   - The command line arguments object.
   """
-  index, args, isolated_hash = args
+  try:
+    return _DoSpawn(args)
+  except Exception as e:
+    traceback.print_exc()
+    return None
+
+
+def _DoSpawn(args):
+  index, args, cas_digest, swarming_command = args
+  runner_args = []
   json_file = os.path.join(args.results, '%d.json' % index)
   trigger_args = [
       'tools/luci-go/swarming',
       'trigger',
       '-S',
       'https://chromium-swarm.appspot.com',
-      '-I',
-      'https://isolateserver.appspot.com',
-      '-d',
-      'pool=' + args.pool,
-      '-s',
-      isolated_hash,
+      '-digest',
+      cas_digest,
       '-dump-json',
       json_file,
-      '-d',
-      'os=' + args.swarming_os,
       '-tag=purpose:user-debug-run-swarmed',
   ]
   if args.target_os == 'fuchsia':
     trigger_args += [
         '-d',
         'kvm=1',
-        '-d',
-        'gpu=none',
     ]
+    if args.gpu is None:
+      trigger_args += [
+          '-d',
+          'gpu=none',
+      ]
+  elif args.target_os == 'android':
+    if args.arch == 'x86':
+      # No x86 Android devices are available in swarming. So assume we want to
+      # run on emulators when building for x86 on Android.
+      args.swarming_os = 'Linux'
+      args.pool = 'chromium.tests.avd'
+      # generic_android28 == Android P emulator. See //tools/android/avd/proto/
+      # for other options.
+      runner_args.append(
+          '--avd-config=../../tools/android/avd/proto/generic_android28.textpb')
+    elif args.device_type is None and args.device_os is None:
+      # The aliases for device type are stored here:
+      # luci/appengine/swarming/ui2/modules/alias.js
+      # for example 'blueline' = 'Pixel 3'
+      trigger_args += ['-d', 'device_type=' + DEFAULT_ANDROID_DEVICE_TYPE]
+  elif args.target_os == 'ios':
+    print('WARNING: iOS support is quite limited.\n'
+          '1) --gtest_filter does not work with unit tests.\n' +
+          '2) Wildcards do not work with EG tests (--gtest_filter=Foo*).\n' +
+          '3) Some arguments are hardcoded (e.g. xcode version) and will ' +
+          'break over time. \n')
+
+    runner_args.append('--xcode-build-version=14c18')
+    runner_args.append('--xctest')
+    runner_args.append('--xcode-parallelization')
+    runner_args.append('--out-dir=./test-data')
+    runner_args.extend(['--platform', 'iPhone 13'])
+    runner_args.append('--version=15.5')
+    trigger_args.extend([
+        '-service-account',
+        'chromium-tester@chops-service-accounts.iam.gserviceaccount.com'
+    ])
+    trigger_args.extend(['-named-cache', 'runtime_ios_15_5=Runtime-ios-15.5'])
+    trigger_args.extend(['-named-cache', 'xcode_ios_14c18=Xcode.app'])
+    trigger_args.extend([
+        '-cipd-package', '.:infra/tools/mac_toolchain/${platform}=' +
+        'git_revision:59ddedfe3849abf560cbe0b41bb8e431041cd2bb'
+    ])
+
   if args.arch != 'detect':
     trigger_args += [
         '-d',
         'cpu=' + args.arch,
     ]
 
-  # The aliases for device type are stored here:
-  # luci/appengine/swarming/ui2/modules/alias.js
-  # for example 'blueline' = 'Pixel 3'
-  if args.target_os == 'android':
-    if args.device_type is None and args.device_os is None:
-      trigger_args += ['-d', 'device_type=' + DEFAULT_ANDROID_DEVICE_TYPE]
   if args.device_type:
     trigger_args += ['-d', 'device_type=' + args.device_type]
 
   if args.device_os:
     trigger_args += ['-d', 'device_os=' + args.device_os]
 
-  runner_args = []
+  if args.gpu:
+    trigger_args += ['-d', 'gpu=' + args.gpu]
+
   if not args.no_test_flags:
     # These flags are recognized by our test runners, but do not work
     # when running custom scripts.
     runner_args += [
-        '--test-launcher-summary-output=${ISOLATED_OUTDIR}/output.json',
-        '--system-log-file=${ISOLATED_OUTDIR}/system_log'
+        '--test-launcher-summary-output=${ISOLATED_OUTDIR}/output.json'
     ]
+    if 'junit' not in args.target_name:
+      runner_args += ['--system-log-file=${ISOLATED_OUTDIR}/system_log']
   if args.gtest_filter:
     runner_args.append('--gtest_filter=' + args.gtest_filter)
-  if args.repeat:
-    runner_args.append('--repeat=' + args.repeat)
+  if args.gtest_repeat:
+    runner_args.append('--gtest_repeat=' + args.gtest_repeat)
+  if args.test_launcher_shard_index and args.test_launcher_total_shards:
+    runner_args.append('--test-launcher-shard-index=' +
+                       args.test_launcher_shard_index)
+    runner_args.append('--test-launcher-total-shards=' +
+                       args.test_launcher_total_shards)
   elif args.target_os == 'fuchsia':
     filter_file = \
         'testing/buildbot/filters/fuchsia.' + args.target_name + '.filter'
     if os.path.isfile(filter_file):
       runner_args.append('--test-launcher-filter-file=../../' + filter_file)
 
-  if runner_args:
-    trigger_args.append('--')
-    trigger_args.extend(runner_args)
+  runner_args.extend(args.runner_args)
+
+  trigger_args.extend(['-d', 'os=' + args.swarming_os])
+  trigger_args.extend(['-d', 'pool=' + args.pool])
+  trigger_args.extend(['--relative-cwd', args.out_dir, '--'])
+  trigger_args.extend(swarming_command)
+  trigger_args.extend(runner_args)
 
   with open(os.devnull, 'w') as nul:
     subprocess.check_call(trigger_args, stdout=nul)
@@ -120,6 +178,9 @@ def _Spawn(args):
 
 
 def _Collect(spawn_result):
+  if spawn_result is None:
+    return 1
+
   index, json_file, args = spawn_result
   with open(json_file) as f:
     task_json = json.load(f)
@@ -146,7 +207,7 @@ def _Collect(spawn_result):
     exit_code = p.returncode
     file_suffix = '' if exit_code == 0 else '.FAILED'
   filename = '%d%s.stdout.txt' % (index, file_suffix)
-  with open(os.path.join(args.results, filename), 'w') as f:
+  with open(os.path.join(args.results, filename), 'wb') as f:
     f.write(stdout)
   return exit_code
 
@@ -157,55 +218,76 @@ def main():
   parser.add_argument('--target-os', default='detect', help='gn target_os')
   parser.add_argument('--arch', '-a', default='detect',
                       help='CPU architecture of the test binary.')
-  parser.add_argument('--build', dest='build', action='store_true',
-                      help='Build before isolating (default).')
-  parser.add_argument( '--no-build', dest='build', action='store_false',
-                      help='Do not build, just isolate.')
+  parser.add_argument('--build',
+                      dest='build',
+                      action='store_true',
+                      help='Build before isolating.')
+  parser.add_argument('--no-build',
+                      dest='build',
+                      action='store_false',
+                      help='Do not build, just isolate (default).')
   parser.add_argument('--isolate-map-file', '-i',
                       help='path to isolate map file if not using default')
   parser.add_argument('--copies', '-n', type=int, default=1,
                       help='Number of copies to spawn.')
   parser.add_argument(
       '--device-os', help='Run tests on the given version of Android.')
-  parser.add_argument(
-      '--device-type',
-      help='device_type specifier for Swarming'
-      ' from https://chromium-swarm.appspot.com/botlist .')
+  parser.add_argument('--device-type',
+                      help='device_type specifier for Swarming'
+                      ' from https://chromium-swarm.appspot.com/botlist .')
+  parser.add_argument('--gpu',
+                      help='gpu specifier for Swarming'
+                      ' from https://chromium-swarm.appspot.com/botlist .')
   parser.add_argument('--pool',
                       default='chromium.tests',
                       help='Use the given swarming pool.')
   parser.add_argument('--results', '-r', default='results',
                       help='Directory in which to store results.')
-  parser.add_argument('--gtest_filter',
-                      help='Use the given gtest_filter, rather than the '
-                           'default filter file, if any.')
   parser.add_argument(
-      '--repeat', help='Number of times to repeat the specified set of tests.')
+      '--gtest_filter',
+      help='Deprecated. Pass as test runner arg instead, like \'-- '
+      '--gtest_filter="*#testFoo"\'')
+  parser.add_argument(
+      '--gtest_repeat',
+      help='Deprecated. Pass as test runner arg instead, like \'-- '
+      '--gtest_repeat=99\'')
+  parser.add_argument(
+      '--test-launcher-shard-index',
+      help='Shard index to run. Use with --test-launcher-total-shards.')
+  parser.add_argument('--test-launcher-total-shards',
+                      help='Number of shards to split the test into. Use with'
+                      ' --test-launcher-shard-index.')
   parser.add_argument('--no-test-flags', action='store_true',
                       help='Do not add --test-launcher-summary-output and '
                            '--system-log-file flags to the comment.')
   parser.add_argument('out_dir', type=str, help='Build directory.')
   parser.add_argument('target_name', type=str, help='Name of target to run.')
+  parser.add_argument(
+      'runner_args',
+      nargs='*',
+      type=str,
+      help='Arguments to pass to the test runner, e.g. gtest_filter and '
+      'gtest_repeat.')
 
-  args = parser.parse_args()
+  args = parser.parse_intermixed_args()
+
+  with open(os.path.join(args.out_dir, 'args.gn')) as f:
+    gn_args = gn_helpers.FromGNArgs(f.read())
 
   if args.target_os == 'detect':
-    with open(os.path.join(args.out_dir, 'args.gn')) as f:
-      gn_args = {}
-      for l in f:
-        l = l.split('#')[0].strip()
-        if not l: continue
-        k, v = map(str.strip, l.split('=', 1))
-        gn_args[k] = v
     if 'target_os' in gn_args:
       args.target_os = gn_args['target_os'].strip('"')
     else:
-      args.target_os = { 'darwin': 'mac', 'linux2': 'linux', 'win32': 'win' }[
-                           sys.platform]
+      args.target_os = {
+          'darwin': 'mac',
+          'linux': 'linux',
+          'win32': 'win'
+      }[sys.platform]
 
   if args.swarming_os is None:
     args.swarming_os = {
       'mac': 'Mac',
+      'ios': 'Mac-13',
       'win': 'Windows',
       'linux': 'Linux',
       'android': 'Android',
@@ -217,13 +299,19 @@ def main():
     args.target_name = os.path.splitext(args.target_name)[0]
 
   # Determine the CPU architecture of the test binary, if not specified.
-  if args.arch == 'detect' and args.target_os not in ('android', 'mac', 'win'):
-    executable_info = subprocess.check_output(
-        ['file', os.path.join(args.out_dir, args.target_name)])
-    if 'ARM aarch64' in executable_info:
-      args.arch = 'arm64',
-    else:
-      args.arch = 'x86-64'
+  if args.arch == 'detect':
+    if args.target_os == 'ios':
+      print('iOS must specify --arch. Probably arm64 or x86-64.')
+      return 1
+    if args.target_os not in ('android', 'mac', 'win'):
+      executable_info = subprocess.check_output(
+          ['file', os.path.join(args.out_dir, args.target_name)], text=True)
+      if 'ARM aarch64' in executable_info:
+        args.arch = 'arm64',
+      else:
+        args.arch = 'x86-64'
+    elif args.target_os == 'android':
+      args.arch = gn_args.get('target_cpu', 'detect')
 
   mb_cmd = [sys.executable, 'tools/mb/mb.py', 'isolate']
   if not args.build:
@@ -231,22 +319,33 @@ def main():
   if args.isolate_map_file:
     mb_cmd += ['--isolate-map-file', args.isolate_map_file]
   mb_cmd += ['//' + args.out_dir, args.target_name]
-  subprocess.check_call(mb_cmd)
+  subprocess.check_call(mb_cmd, shell=os.name == 'nt')
 
   print('If you get authentication errors, follow:')
   print(
-      '  https://www.chromium.org/developers/testing/isolated-testing/for-swes#TOC-Login-on-the-services'
+      '  https://chromium.googlesource.com/chromium/src/+/HEAD/docs/workflow/debugging-with-swarming.md#authenticating'
   )
 
   print('Uploading to isolate server, this can take a while...')
-  isolated = os.path.join(args.out_dir, args.target_name + '.isolated')
+  isolate = os.path.join(args.out_dir, args.target_name + '.isolate')
+  archive_json = os.path.join(args.out_dir, args.target_name + '.archive.json')
   subprocess.check_output([
-      'tools/luci-go/isolate', 'archive', '-I',
-      'https://isolateserver.appspot.com', '-i',
-      os.path.join(args.out_dir, args.target_name + '.isolate'), '-s', isolated
+      'tools/luci-go/isolate', 'archive', '-cas-instance', 'chromium-swarm',
+      '-isolate', isolate, '-dump-json', archive_json
   ])
-  with open(isolated) as f:
-    isolated_hash = hashlib.sha1(f.read()).hexdigest()
+  with open(archive_json) as f:
+    cas_digest = json.load(f).get(args.target_name)
+
+  mb_cmd = [
+      sys.executable, 'tools/mb/mb.py', 'get-swarming-command', '--as-list'
+  ]
+  if not args.build:
+    mb_cmd.append('--no-build')
+  if args.isolate_map_file:
+    mb_cmd += ['--isolate-map-file', args.isolate_map_file]
+  mb_cmd += ['//' + args.out_dir, args.target_name]
+  mb_output = subprocess.check_output(mb_cmd, shell=os.name == 'nt')
+  swarming_cmd = json.loads(mb_output)
 
   if os.path.isdir(args.results):
     shutil.rmtree(args.results)
@@ -257,7 +356,8 @@ def main():
     # Use dummy since threadpools give better exception messages
     # than process pools do, and threads work fine for what we're doing.
     pool = multiprocessing.dummy.Pool()
-    spawn_args = map(lambda i: (i, args, isolated_hash), range(args.copies))
+    spawn_args = [(i, args, cas_digest, swarming_cmd)
+                  for i in range(args.copies)]
     spawn_results = pool.imap_unordered(_Spawn, spawn_args)
 
     exit_codes = []