2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
19 sys
.path
.append(os
.path
.join(os
.path
.dirname(__file__
), '..'))
20 import provision_devices
21 from pylib
import android_commands
22 from pylib
import constants
23 from pylib
.gtest
import gtest_config
25 CHROME_SRC_DIR
= bb_utils
.CHROME_SRC
26 CHROME_OUT_DIR
= bb_utils
.CHROME_OUT_DIR
27 sys
.path
.append(os
.path
.join(
28 CHROME_SRC_DIR
, 'third_party', 'android_testrunner'))
32 SLAVE_SCRIPTS_DIR
= os
.path
.join(bb_utils
.BB_BUILD_DIR
, 'scripts', 'slave')
33 LOGCAT_DIR
= os
.path
.join(bb_utils
.CHROME_OUT_DIR
, 'logcat')
34 GS_URL
= 'https://storage.googleapis.com'
36 # Describes an instrumation test suite:
37 # test: Name of test we're running.
38 # apk: apk to be installed.
39 # apk_package: package for the apk to be installed.
40 # test_apk: apk to run tests on.
41 # test_data: data folder in format destination:source.
42 # host_driven_root: The host-driven test root directory.
43 # annotation: Annotation of the tests to include.
44 # exclude_annotation: The annotation of the tests to exclude.
45 I_TEST
= collections
.namedtuple('InstrumentationTest', [
46 'name', 'apk', 'apk_package', 'test_apk', 'test_data', 'host_driven_root',
47 'annotation', 'exclude_annotation', 'extra_flags'])
49 def I(name
, apk
, apk_package
, test_apk
, test_data
, host_driven_root
=None,
50 annotation
=None, exclude_annotation
=None, extra_flags
=None):
51 return I_TEST(name
, apk
, apk_package
, test_apk
, test_data
, host_driven_root
,
52 annotation
, exclude_annotation
, extra_flags
)
54 INSTRUMENTATION_TESTS
= dict((suite
.name
, suite
) for suite
in [
57 'org.chromium.content_shell_apk',
59 'content:content/test/data/android/device_files'),
60 I('ChromiumTestShell',
61 'ChromiumTestShell.apk',
62 'org.chromium.chrome.testshell',
63 'ChromiumTestShellTest',
64 'chrome:chrome/test/data/android/device_files',
65 constants
.CHROMIUM_TEST_SHELL_HOST_DRIVEN_DIR
),
68 'org.chromium.android_webview.shell',
70 'webview:android_webview/test/data/device_files'),
73 VALID_TESTS
= set(['chromedriver', 'gpu', 'ui', 'unit', 'webkit',
74 'webkit_layout', 'webrtc'])
76 RunCmd
= bb_utils
.RunCmd
79 # multiprocessing map_async requires a top-level function for pickle library.
80 def RebootDeviceSafe(device
):
81 """Reboot a device, wait for it to start, and squelch timeout exceptions."""
83 android_commands
.AndroidCommands(device
).Reboot(True)
84 except errors
.DeviceUnresponsiveError
as e
:
89 """Reboot all attached and online devices."""
90 # Early return here to avoid presubmit dependence on adb,
91 # which might not exist in this checkout.
94 devices
= android_commands
.GetAttachedDevices(emulator
=False)
95 print 'Rebooting: %s' % devices
97 pool
= multiprocessing
.Pool(len(devices
))
98 results
= pool
.map_async(RebootDeviceSafe
, devices
).get(99999)
100 for device
, result
in zip(devices
, results
):
102 print '%s failed to startup.' % device
105 bb_annotations
.PrintWarning()
107 print 'Reboots complete.'
110 def RunTestSuites(options
, suites
):
111 """Manages an invocation of test_runner.py for gtests.
114 options: options object.
115 suites: List of suite names to run.
118 if options
.target
== 'Release':
119 args
.append('--release')
121 args
.append('--tool=asan')
123 bb_annotations
.PrintNamedStep(suite
)
124 cmd
= ['build/android/test_runner.py', 'gtest', '-s', suite
] + args
125 if suite
== 'content_browsertests':
126 cmd
.append('--num_retries=1')
129 def RunChromeDriverTests(_
):
130 """Run all the steps for running chromedriver tests."""
131 bb_annotations
.PrintNamedStep('chromedriver_annotation')
132 RunCmd(['chrome/test/chromedriver/run_buildbot_steps.py',
133 '--android-packages=%s,%s,%s' %
134 (constants
.PACKAGE_INFO
['chromium_test_shell'].package
,
135 constants
.PACKAGE_INFO
['chrome_stable'].package
,
136 constants
.PACKAGE_INFO
['chrome_beta'].package
)])
138 def InstallApk(options
, test
, print_step
=False):
139 """Install an apk to all phones.
142 options: options object
143 test: An I_TEST namedtuple
144 print_step: Print a buildbot step
147 bb_annotations
.PrintNamedStep('install_%s' % test
.name
.lower())
148 # TODO(gkanwar): Quick hack to make sure AndroidWebViewTest.apk is replaced
149 # before AndroidWebView.apk is. This can be removed once the bots cycle.
150 args
= ['--apk', '%s.apk' % test
.test_apk
]
151 if options
.target
== 'Release':
152 args
.append('--release')
154 RunCmd(['build/android/adb_install_apk.py'] + args
, halt_on_failure
=True)
156 args
= ['--apk', test
.apk
, '--apk_package', test
.apk_package
]
157 if options
.target
== 'Release':
158 args
.append('--release')
160 RunCmd(['build/android/adb_install_apk.py'] + args
, halt_on_failure
=True)
163 def RunInstrumentationSuite(options
, test
, flunk_on_failure
=True,
164 python_only
=False, official_build
=False):
165 """Manages an invocation of test_runner.py for instrumentation tests.
168 options: options object
169 test: An I_TEST namedtuple
170 flunk_on_failure: Flunk the step if tests fail.
171 Python: Run only host driven Python tests.
172 official_build: Run official-build tests.
174 bb_annotations
.PrintNamedStep('%s_instrumentation_tests' % test
.name
.lower())
176 InstallApk(options
, test
)
177 args
= ['--test-apk', test
.test_apk
, '--test_data', test
.test_data
,
179 if options
.target
== 'Release':
180 args
.append('--release')
182 args
.append('--tool=asan')
183 if options
.flakiness_server
:
184 args
.append('--flakiness-dashboard-server=%s' %
185 options
.flakiness_server
)
186 if options
.coverage_bucket
:
187 args
.append('--coverage-dir=%s' % options
.coverage_dir
)
188 if test
.host_driven_root
:
189 args
.append('--host-driven-root=%s' % test
.host_driven_root
)
191 args
.extend(['-A', test
.annotation
])
192 if test
.exclude_annotation
:
193 args
.extend(['-E', test
.exclude_annotation
])
195 args
.extend(test
.extra_flags
)
199 # The option needs to be assigned 'True' as it does not have an action
200 # associated with it.
201 args
.append('--official-build')
203 RunCmd(['build/android/test_runner.py', 'instrumentation'] + args
,
204 flunk_on_failure
=flunk_on_failure
)
207 def RunWebkitLint(target
):
208 """Lint WebKit's TestExpectation files."""
209 bb_annotations
.PrintNamedStep('webkit_lint')
210 RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py',
216 def RunWebkitLayoutTests(options
):
217 """Run layout tests on an actual device."""
218 bb_annotations
.PrintNamedStep('webkit_tests')
221 '--no-new-test-results',
222 '--full-results-html',
223 '--clobber-old-results',
224 '--exit-after-n-failures', '5000',
225 '--exit-after-n-crashes-or-timeouts', '100',
226 '--debug-rwt-logging',
227 '--results-directory', '../layout-test-results',
228 '--target', options
.target
,
229 '--builder-name', options
.build_properties
.get('buildername', ''),
230 '--build-number', str(options
.build_properties
.get('buildnumber', '')),
231 '--master-name', 'ChromiumWebkit', # TODO: Get this from the cfg.
232 '--build-name', options
.build_properties
.get('buildername', ''),
233 '--platform=android']
235 for flag
in 'test_results_server', 'driver_name', 'additional_drt_flag':
236 if flag
in options
.factory_properties
:
237 cmd_args
.extend(['--%s' % flag
.replace('_', '-'),
238 options
.factory_properties
.get(flag
)])
240 for f
in options
.factory_properties
.get('additional_expectations', []):
242 ['--additional-expectations=%s' % os
.path
.join(CHROME_SRC_DIR
, *f
)])
244 # TODO(dpranke): Remove this block after
245 # https://codereview.chromium.org/12927002/ lands.
246 for f
in options
.factory_properties
.get('additional_expectations_files', []):
248 ['--additional-expectations=%s' % os
.path
.join(CHROME_SRC_DIR
, *f
)])
250 RunCmd(['webkit/tools/layout_tests/run_webkit_tests.py'] + cmd_args
)
252 if options
.factory_properties
.get('archive_webkit_results', False):
253 bb_annotations
.PrintNamedStep('archive_webkit_results')
254 base
= 'https://storage.googleapis.com/chromium-layout-test-archives'
255 builder_name
= options
.build_properties
.get('buildername', '')
256 build_number
= str(options
.build_properties
.get('buildnumber', ''))
257 bb_annotations
.PrintLink('results',
258 '%s/%s/%s/layout-test-results/results.html' % (
259 base
, EscapeBuilderName(builder_name
), build_number
))
260 bb_annotations
.PrintLink('(zip)', '%s/%s/%s/layout-test-results.zip' % (
261 base
, EscapeBuilderName(builder_name
), build_number
))
262 gs_bucket
= 'gs://chromium-layout-test-archives'
263 RunCmd([os
.path
.join(SLAVE_SCRIPTS_DIR
, 'chromium',
264 'archive_layout_test_results.py'),
265 '--results-dir', '../../layout-test-results',
266 '--build-dir', CHROME_OUT_DIR
,
267 '--build-number', build_number
,
268 '--builder-name', builder_name
,
269 '--gs-bucket', gs_bucket
])
272 def EscapeBuilderName(builder_name
):
273 return re
.sub('[ ()]', '_', builder_name
)
276 def SpawnLogcatMonitor():
277 shutil
.rmtree(LOGCAT_DIR
, ignore_errors
=True)
279 os
.path
.join(CHROME_SRC_DIR
, 'build', 'android', 'adb_logcat_monitor.py'),
282 # Wait for logcat_monitor to pull existing logcat
283 RunCmd(['sleep', '5'])
285 def ProvisionDevices(options
):
286 bb_annotations
.PrintNamedStep('provision_devices')
288 if not bb_utils
.TESTING
:
289 # Restart adb to work around bugs, sleep to wait for usb discovery.
290 adb
= android_commands
.AndroidCommands()
291 adb
.RestartAdbServer()
292 RunCmd(['sleep', '1'])
296 provision_cmd
= ['build/android/provision_devices.py', '-t', options
.target
]
297 if options
.auto_reconnect
:
298 provision_cmd
.append('--auto-reconnect')
299 RunCmd(provision_cmd
)
302 def DeviceStatusCheck(_
):
303 bb_annotations
.PrintNamedStep('device_status_check')
304 RunCmd(['build/android/buildbot/bb_device_status_check.py'],
305 halt_on_failure
=True)
308 def GetDeviceSetupStepCmds():
310 ('provision_devices', ProvisionDevices
),
311 ('device_status_check', DeviceStatusCheck
),
315 def RunUnitTests(options
):
316 RunTestSuites(options
, gtest_config
.STABLE_TEST_SUITES
)
319 def RunInstrumentationTests(options
):
320 for test
in INSTRUMENTATION_TESTS
.itervalues():
321 RunInstrumentationSuite(options
, test
)
324 def RunWebkitTests(options
):
325 RunTestSuites(options
, ['webkit_unit_tests'])
326 RunWebkitLint(options
.target
)
329 def RunWebRTCTests(options
):
330 RunTestSuites(options
, gtest_config
.WEBRTC_TEST_SUITES
)
333 def RunGPUTests(options
):
334 InstallApk(options
, INSTRUMENTATION_TESTS
['ContentShell'], False)
335 bb_annotations
.PrintNamedStep('gpu_tests')
336 RunCmd(['content/test/gpu/run_gpu_test',
337 '--browser=android-content-shell', 'pixel'])
340 def GetTestStepCmds():
342 ('chromedriver', RunChromeDriverTests
),
343 ('gpu', RunGPUTests
),
344 ('unit', RunUnitTests
),
345 ('ui', RunInstrumentationTests
),
346 ('webkit', RunWebkitTests
),
347 ('webkit_layout', RunWebkitLayoutTests
),
348 ('webrtc', RunWebRTCTests
),
352 def UploadHTML(options
, gs_base_dir
, dir_to_upload
, link_text
,
353 link_rel_path
='index.html', gs_url
=GS_URL
):
354 """Uploads directory at |dir_to_upload| to Google Storage and output a link.
357 options: Command line options.
358 gs_base_dir: The Google Storage base directory (e.g.
359 'chromium-code-coverage/java')
360 dir_to_upload: Absolute path to the directory to be uploaded.
361 link_text: Link text to be displayed on the step.
362 link_rel_path: Link path relative to |dir_to_upload|.
363 gs_url: Google storage URL.
365 revision
= options
.build_properties
.get('got_revision')
367 revision
= options
.build_properties
.get('revision', 'testing')
368 bot_id
= options
.build_properties
.get('buildername', 'testing')
369 randhash
= hashlib
.sha1(str(random
.random())).hexdigest()
370 gs_path
= '%s/%s/%s/%s' % (gs_base_dir
, bot_id
, revision
, randhash
)
371 RunCmd([bb_utils
.GSUTIL_PATH
, 'cp', '-R', dir_to_upload
, 'gs://%s' % gs_path
])
372 bb_annotations
.PrintLink(link_text
,
373 '%s/%s/%s' % (gs_url
, gs_path
, link_rel_path
))
376 def GenerateJavaCoverageReport(options
):
377 """Generates an HTML coverage report using EMMA and uploads it."""
378 bb_annotations
.PrintNamedStep('java_coverage_report')
380 coverage_html
= os
.path
.join(options
.coverage_dir
, 'coverage_html')
381 RunCmd(['build/android/generate_emma_html.py',
382 '--coverage-dir', options
.coverage_dir
,
383 '--metadata-dir', os
.path
.join(CHROME_OUT_DIR
, options
.target
),
385 '--output', os
.path
.join(coverage_html
, 'index.html')])
389 def LogcatDump(options
):
390 # Print logcat, kill logcat monitor
391 bb_annotations
.PrintNamedStep('logcat_dump')
392 logcat_file
= os
.path
.join(CHROME_OUT_DIR
, options
.target
, 'full_log')
393 with
open(logcat_file
, 'w') as f
:
395 os
.path
.join(CHROME_SRC_DIR
, 'build', 'android',
396 'adb_logcat_printer.py'),
397 LOGCAT_DIR
], stdout
=f
)
398 RunCmd(['cat', logcat_file
])
401 def GenerateTestReport(options
):
402 bb_annotations
.PrintNamedStep('test_report')
403 for report
in glob
.glob(
404 os
.path
.join(CHROME_OUT_DIR
, options
.target
, 'test_logs', '*.log')):
405 RunCmd(['cat', report
])
409 def MainTestWrapper(options
):
411 # Spawn logcat monitor
414 # Run all device setup steps
415 for _
, cmd
in GetDeviceSetupStepCmds():
419 test_obj
= INSTRUMENTATION_TESTS
[options
.install
]
420 InstallApk(options
, test_obj
, print_step
=True)
422 if options
.test_filter
:
423 bb_utils
.RunSteps(options
.test_filter
, GetTestStepCmds(), options
)
425 if options
.coverage_bucket
:
426 coverage_html
= GenerateJavaCoverageReport(options
)
427 UploadHTML(options
, '%s/java' % options
.coverage_bucket
, coverage_html
,
430 if options
.experimental
:
431 RunTestSuites(options
, gtest_config
.EXPERIMENTAL_TEST_SUITES
)
434 # Run all post test steps
436 GenerateTestReport(options
)
437 # KillHostHeartbeat() has logic to check if heartbeat process is running,
438 # and kills only if it finds the process is running on the host.
439 provision_devices
.KillHostHeartbeat()
442 def GetDeviceStepsOptParser():
443 parser
= bb_utils
.GetParser()
444 parser
.add_option('--experimental', action
='store_true',
445 help='Run experiemental tests')
446 parser
.add_option('-f', '--test-filter', metavar
='<filter>', default
=[],
448 help=('Run a test suite. Test suites: "%s"' %
449 '", "'.join(VALID_TESTS
)))
450 parser
.add_option('--asan', action
='store_true', help='Run tests with asan.')
451 parser
.add_option('--install', metavar
='<apk name>',
452 help='Install an apk by name')
453 parser
.add_option('--reboot', action
='store_true',
454 help='Reboot devices before running tests')
455 parser
.add_option('--coverage-bucket',
456 help=('Bucket name to store coverage results. Coverage is '
457 'only run if this is set.'))
459 '--flakiness-server',
460 help='The flakiness dashboard server to which the results should be '
463 '--auto-reconnect', action
='store_true',
464 help='Push script to device which restarts adbd on disconnections.')
466 '--logcat-dump-output',
467 help='The logcat dump output will be "tee"-ed into this file')
473 parser
= GetDeviceStepsOptParser()
474 options
, args
= parser
.parse_args(argv
[1:])
477 return sys
.exit('Unused args %s' % args
)
479 unknown_tests
= set(options
.test_filter
) - VALID_TESTS
481 return sys
.exit('Unknown tests %s' % list(unknown_tests
))
483 setattr(options
, 'target', options
.factory_properties
.get('target', 'Debug'))
484 if options
.coverage_bucket
:
485 setattr(options
, 'coverage_dir',
486 os
.path
.join(CHROME_OUT_DIR
, options
.target
, 'coverage'))
488 MainTestWrapper(options
)
491 if __name__
== '__main__':
492 sys
.exit(main(sys
.argv
))