summaryrefslogtreecommitdiff
path: root/chromium/build/fuchsia/test_runner.py
blob: 42506bef57f840ba3c166623e105e5018ea6d93c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env python
#
# Copyright 2017 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.

"""Packages a user.bootfs for a Fuchsia boot image, pulling in the runtime
dependencies of a test binary, and then uses either QEMU from the Fuchsia SDK
to run, or starts the bootserver to allow running on a hardware device."""

import argparse
import json
import os
import socket
import sys
import tempfile
import time

from runner_common import AddRunnerCommandLineArguments, BuildBootfs, \
    ImageCreationData, ReadRuntimeDeps, RunFuchsia, HOST_IP_ADDRESS

DIR_SOURCE_ROOT = os.path.abspath(
    os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
sys.path.append(os.path.join(DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common'))
import chrome_test_server_spawner


def IsLocalPortAvailable(port):
  s = socket.socket()
  try:
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('127.0.0.1', port))
    return True
  except socket.error:
    return False
  finally:
    s.close()


def WaitUntil(predicate, timeout_seconds=1):
  """Blocks until the provided predicate (function) is true.

  Returns:
    Whether the provided predicate was satisfied once (before the timeout).
  """
  start_time = time.clock()
  sleep_time_sec = 0.025
  while True:
    if predicate():
      return True

    if time.clock() - start_time > timeout_seconds:
      return False

    time.sleep(sleep_time_sec)
    sleep_time_sec = min(1, sleep_time_sec * 2)  # Don't wait more than 1 sec.


# Implementation of chrome_test_server_spawner.PortForwarder that doesn't
# forward ports. Instead the tests are expected to connect to the host IP
# address inside the virtual network provided by qemu. qemu will forward
# these connections to the corresponding localhost ports.
class PortForwarderNoop(chrome_test_server_spawner.PortForwarder):
  def Map(self, port_pairs):
    pass

  def GetDevicePortForHostPort(self, host_port):
    return host_port

  def WaitHostPortAvailable(self, port):
    return WaitUntil(lambda: IsLocalPortAvailable(port))

  def WaitPortNotAvailable(self, port):
    return WaitUntil(lambda: not IsLocalPortAvailable(port))

  def WaitDevicePortReady(self, port):
    return self.WaitPortNotAvailable(port)

  def Unmap(self, device_port):
    pass


def main():
  parser = argparse.ArgumentParser()
  AddRunnerCommandLineArguments(parser)
  parser.add_argument('--enable-test-server', action='store_true',
                      default=False,
                      help='Enable testserver spawner.')
  parser.add_argument('--gtest_filter',
                      help='GTest filter to use in place of any default.')
  parser.add_argument('--gtest_repeat',
                      help='GTest repeat value to use. This also disables the '
                           'test launcher timeout.')
  parser.add_argument('--gtest_break_on_failure', action='store_true',
                      default=False,
                      help='Should GTest break on failure; useful with '
                           '--gtest_repeat.')
  parser.add_argument('--single-process-tests', action='store_true',
                      default=False,
                      help='Runs the tests and the launcher in the same '
                      'process. Useful for debugging.')
  parser.add_argument('--test-launcher-batch-limit',
                      type=int,
                      help='Sets the limit of test batch to run in a single '
                      'process.')
  # --test-launcher-filter-file is specified relative to --output-directory,
  # so specifying type=os.path.* will break it.
  parser.add_argument('--test-launcher-filter-file',
                      default=None,
                      help='Override default filter file passed to target test '
                      'process. Set an empty path to disable filtering.')
  parser.add_argument('--test-launcher-jobs',
                      type=int,
                      help='Sets the number of parallel test jobs.')
  parser.add_argument('--test-launcher-summary-output',
                      '--test_launcher_summary_output',
                      help='Where the test launcher will output its json.')
  parser.add_argument('child_args', nargs='*',
                      help='Arguments for the test process.')
  args = parser.parse_args()

  child_args = ['--test-launcher-retry-limit=0']

  if args.single_process_tests:
    child_args.append('--single-process-tests')

  if args.test_launcher_batch_limit:
    child_args.append('--test-launcher-batch-limit=%d' %
                       args.test_launcher_batch_limit)

  # By default run the same number of test jobs as there are CPU cores.
  # If running tests on a device then the caller should provide the
  # test-launcher-jobs limit explicitly, since we can't count the CPU cores.
  test_concurrency = args.test_launcher_jobs \
      if args.test_launcher_jobs else args.vm_cpu_cores
  child_args.append('--test-launcher-jobs=%d' % test_concurrency)

  if args.gtest_filter:
    child_args.append('--gtest_filter=' + args.gtest_filter)
  if args.gtest_repeat:
    child_args.append('--gtest_repeat=' + args.gtest_repeat)
    child_args.append('--test-launcher-timeout=-1')
  if args.gtest_break_on_failure:
    child_args.append('--gtest_break_on_failure')
  if args.child_args:
    child_args.extend(args.child_args)

  runtime_deps = ReadRuntimeDeps(args.runtime_deps_path, args.output_directory)

  spawning_server = None

  # Start test server spawner for tests that need it.
  if args.enable_test_server:
    spawning_server = chrome_test_server_spawner.SpawningServer(
        0, PortForwarderNoop(), test_concurrency)
    spawning_server.Start()

    # Generate test server config.
    config_file = tempfile.NamedTemporaryFile()
    config_file.write(json.dumps({
      'name': 'testserver',
      'address': HOST_IP_ADDRESS,
      'spawner_url_base': 'http://%s:%d' %
          (HOST_IP_ADDRESS, spawning_server.server_port)
    }))
    config_file.flush()
    runtime_deps.append(('net-test-server-config', config_file.name))

  # If no --test-launcher-filter-file is specified, use the default filter path.
  if args.test_launcher_filter_file == None:
    exe_base_name = os.path.basename(args.exe_name)
    test_launcher_filter_file = os.path.normpath(os.path.join(
        args.output_directory,
        '../../testing/buildbot/filters/fuchsia.%s.filter' % exe_base_name))
    if os.path.exists(test_launcher_filter_file):
      args.test_launcher_filter_file = test_launcher_filter_file

  # Copy the test-launcher-filter-file to the bootfs, if set.
  if args.test_launcher_filter_file:
    # Bundle the filter file in the runtime deps and compose the command-line
    # flag which references it.
    test_launcher_filter_file = os.path.normpath(
        os.path.join(args.output_directory, args.test_launcher_filter_file))
    runtime_deps.append(('test_filter_file', test_launcher_filter_file))
    child_args.append('--test-launcher-filter-file=/system/test_filter_file')

  if args.dry_run:
    print 'Filter file is %s' % (args.test_launcher_filter_file if
                                 args.test_launcher_filter_file else
                                 'not applied.')

  try:
    image_creation_data = ImageCreationData(
        output_directory=args.output_directory,
        exe_name=args.exe_name,
        runtime_deps=runtime_deps,
        target_cpu=args.target_cpu,
        dry_run=args.dry_run,
        child_args=child_args,
        use_device=args.device,
        bootdata=args.bootdata,
        summary_output=args.test_launcher_summary_output,
        shutdown_machine=True,
        wait_for_network=args.wait_for_network,
        use_autorun=True)
    bootfs = BuildBootfs(image_creation_data)
    if not bootfs:
      return 2

    return RunFuchsia(bootfs, args.device, args.kernel, args.dry_run,
                      test_launcher_summary_output=
                          args.test_launcher_summary_output,
                      vm_cpu_cores = args.vm_cpu_cores)
  finally:
    # Stop the spawner to make sure it doesn't leave testserver running, in
    # case some tests failed.
    if spawning_server:
      spawning_server.Stop()


if __name__ == '__main__':
  sys.exit(main())