| # Copyright 2017 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Recipe for building Fuchsia and running tests.""" |
| |
| from recipe_engine.config import Enum, List, Set |
| from recipe_engine.recipe_api import Property |
| |
| TARGETS = ['arm64', 'x64'] |
| |
| BUILD_TYPES = ['debug', 'release', 'thinlto', 'lto'] |
| |
| DEVICES = [ |
| 'QEMU', 'Intel NUC Kit NUC7i5DNHE', 'Khadas Vim2 Max', |
| ] |
| |
| DEPS = [ |
| 'infra/fuchsia', |
| 'infra/gsutil', |
| 'infra/hash', |
| 'infra/jiri', |
| 'infra/tar', |
| 'infra/testsharder', |
| 'recipe_engine/buildbucket', |
| 'recipe_engine/cipd', |
| 'recipe_engine/context', |
| 'recipe_engine/file', |
| 'recipe_engine/path', |
| 'recipe_engine/properties', |
| 'recipe_engine/python', |
| 'recipe_engine/step', |
| ] |
| |
| PROPERTIES = { |
| # -----------------------CHECKOUT-related properties------------------------ |
| # NOTE: These properties are ignored if checkout_snapshot is True. |
| 'project': |
| Property(kind=str, help='Jiri remote manifest project', default=None), |
| 'manifest': |
| Property(kind=str, help='Jiri manifest to use', default=None), |
| 'remote': |
| Property(kind=str, help='Remote manifest repository', default=None), |
| 'repo': |
| Property(kind=str, help='Repo to checkout, build, and test', default=None), |
| 'checkout_snapshot': |
| Property( |
| kind=bool, |
| help='Whether or not to checkout from a Jiri snapshot.' |
| ' Snapshot is expected to be found at revision in repository.', |
| default=False), |
| |
| # --------------------------BUILD-related properties------------------------ |
| 'target': |
| Property(kind=Enum(*TARGETS), help='Target to build'), |
| 'build_type': |
| Property( |
| kind=Enum(*BUILD_TYPES), help='The build type', default='debug'), |
| 'packages': |
| Property(kind=List(basestring), help='Packages to build', default=[]), |
| 'variants': |
| Property( |
| kind=List(basestring), |
| help='--variant arguments to GN in `select_variant`', |
| default=[]), |
| 'gn_args': |
| Property( |
| kind=List(basestring), help='Extra args to pass to GN', default=[]), |
| 'ninja_targets': |
| Property( |
| kind=Set(basestring), |
| help='A set of extra target args to pass to ninja', |
| default=[]), |
| 'board': |
| Property(kind=str, help='Board to build', default=None), |
| 'product': |
| Property(kind=str, help='Product to build', default=None), |
| 'zircon_args': |
| Property( |
| kind=List(basestring), |
| help= |
| 'Additional args to pass to zircon build using standard FOO=bar syntax.', |
| default=[]), |
| 'include_breakpad_symbols': |
| Property( |
| kind=bool, |
| help='Whether to build and upload breakpad_symbols', |
| default=False), |
| 'include_symbol_archive': |
| Property( |
| kind=bool, |
| help='Whether to build and upload a tar archive of all symbolized binaries', |
| default=False), |
| |
| # ---------------------------TEST-related properties------------------------ |
| 'test_in_shards': |
| Property( |
| kind=bool, |
| help='Whether to run tests as shards', |
| default=False), |
| 'run_tests': |
| Property(kind=bool, help='Whether to run target tests', default=False), |
| 'runtests_args': |
| Property( |
| kind=str, |
| help='Shell-quoted string to add to the runtests commandline', |
| default=''), |
| 'run_host_tests': |
| Property(kind=bool, help='Whether to run host tests', default=False), |
| 'test_pool': |
| Property( |
| kind=str, |
| help='Swarming pool from which a test task will be drawn', |
| default='fuchsia.tests'), |
| 'environment_label': |
| Property( |
| kind=str, |
| help='A label of environments on which the testsharder will key', |
| default=''), |
| 'device_type': |
| Property( |
| kind=Enum(*DEVICES), |
| help='The type of device to execute tests on, if the value is' |
| ' not QEMU it will be passed to Swarming as the device_type' |
| ' dimension', |
| default='QEMU'), |
| 'networking_for_tests': |
| Property( |
| kind=bool, |
| help='Whether tests should have access to the network' |
| ' (if True, will cause a failure if tryjob is True or' |
| ' if device_type != QEMU)', |
| default=False), |
| 'pave': |
| Property( |
| kind=bool, |
| help='Whether to pave images the device for testing. (Ignored if' |
| ' device_type == QEMU)', |
| default=True), |
| 'test_timeout_secs': |
| Property( |
| kind=int, |
| help='How long to wait until timing out on tests', |
| default=40 * 60), |
| 'requires_secrets': |
| Property( |
| kind=bool, |
| help='Whether any plaintext needs to be supplied to the tests', |
| default=False), |
| |
| # ---------------------------UPLOAD-related properties---------------------- |
| 'gcs_bucket': |
| Property( |
| kind=str, |
| help='GCS bucket for uploading checkout, build, and test results', |
| default=''), |
| } |
| |
| |
| def RunSteps(api, |
| project, manifest, remote, repo, checkout_snapshot, |
| target, build_type, packages, variants, gn_args, ninja_targets, |
| board, product, zircon_args, include_breakpad_symbols, |
| include_symbol_archive, test_in_shards, run_tests, runtests_args, |
| run_host_tests, test_pool, environment_label, device_type, |
| networking_for_tests, pave, test_timeout_secs, requires_secrets, |
| gcs_bucket): |
| tryjob = api.properties.get('tryjob') |
| upload_results = not tryjob and gcs_bucket |
| |
| # Handle illegal setting of networking_for_tests. |
| if networking_for_tests: |
| # We must make absolutely sure that networking_for_tests is never set in a |
| # tryjob, because a tryjob may execute unvetted code. Letting that code |
| # access the internet can lead to abuse of the CQ system for botnets, among |
| # other things. |
| if tryjob or device_type != 'QEMU': |
| raise api.step.InfraFailure( |
| 'networking for tests is not available for tryjobs and ' |
| 'is not yet implemented for non-QEMU tests') |
| |
| # Handle illegal settings around secrets. |
| if requires_secrets: |
| if tryjob or not networking_for_tests or device_type != 'QEMU': |
| raise api.step.InfraFailure( |
| 'the secrets pipeline is only supported in tryjobs, ' + |
| 'when networking for tests enabled, and ' + 'and on QEMU') |
| |
| build = api.buildbucket.build |
| if api.properties.get('tryjob'): |
| assert len(build.input.gerrit_changes) == 1 |
| |
| if checkout_snapshot: |
| if api.properties.get('tryjob'): |
| checkout = api.fuchsia.checkout_patched_snapshot( |
| gerrit_change=build.input.gerrit_changes[0],) |
| else: |
| checkout = api.fuchsia.checkout_snapshot(gitiles_commit=build.input.gitiles_commit,) |
| else: |
| assert manifest |
| assert remote |
| checkout = api.fuchsia.checkout( |
| build=build, |
| manifest=manifest, |
| remote=remote, |
| project=project, |
| ) |
| |
| if upload_results: |
| checkout.upload_results(gcs_bucket) |
| |
| with api.step.nest('validate checkout'): |
| with api.step.nest('ensure json validator'): |
| with api.context(infra_steps=True): |
| cipd_dir = api.path['start_dir'].join('cipd', 'json_validator') |
| pkgs = api.cipd.EnsureFile() |
| pkgs.add_package('fuchsia/tools/json_validator/${platform}', 'latest') |
| api.cipd.ensure(cipd_dir, pkgs) |
| |
| validator = cipd_dir.join('json_validator') |
| |
| if repo: |
| if repo.startswith('vendor/'): |
| vendor = repo[len('vendor/'):] |
| layer_args = [ |
| '--vendor-layer', |
| vendor, |
| ] |
| namespace_args = [ |
| '--namespaces', |
| vendor, |
| ] |
| else: |
| layer_args = [ |
| '--layer', |
| repo, |
| ] |
| namespace_args = [] |
| |
| api.python( |
| 'validate FIDL namespaces', |
| api.path['start_dir'].join('scripts', 'style', |
| 'verify-fidl-libraries.py'), |
| args=layer_args + namespace_args) |
| |
| api.python( |
| 'validate build packages', |
| api.path['start_dir'].join('scripts', 'packages', 'verify_layer.py'), |
| args=layer_args + [ |
| '--json-validator', |
| validator, |
| ]) |
| |
| # Convert to Set to avoid adding duplicate targets by mistake. Revert to list afterward. |
| ninja_targets = set(ninja_targets) |
| |
| if include_breakpad_symbols: |
| ninja_targets.add('build/gn:breakpad_symbols') |
| if include_symbol_archive: |
| ninja_targets.add('symbol-archive.tgz') |
| |
| ninja_targets = list(ninja_targets) |
| |
| build = api.fuchsia.build( |
| target=target, |
| build_type=build_type, |
| packages=packages, |
| variants=variants, |
| gn_args=gn_args, |
| ninja_targets=ninja_targets, |
| board=board, |
| product=product, |
| zircon_args=zircon_args, |
| collect_build_metrics=upload_results, |
| build_for_testing=run_tests or test_in_shards, |
| # Mac builders cannot handle the strain of building the archive, so |
| # cleanest just to turn off building the archives when running host tests. |
| # |
| # In all other situations, we build the archives even though we might not |
| # upload or use them; this is so that CQ and CI compilations exercise |
| # the same code, a trade-off to avoid surprise CI breakages. |
| build_archive=not run_host_tests, |
| build_package_archive=not run_host_tests, |
| ) |
| |
| if upload_results: |
| build.upload_results( |
| gcs_bucket=gcs_bucket, |
| include_breakpad_symbols=include_breakpad_symbols, |
| include_symbol_archive=include_symbol_archive, |
| ) |
| |
| if run_tests: |
| if test_in_shards: |
| all_results = api.fuchsia.test_in_shards( |
| test_pool=test_pool, |
| build=build, |
| environment_label=environment_label, |
| timeout_secs=test_timeout_secs, |
| ) |
| with api.step.defer_results(): |
| for test_results in all_results: |
| test_results.raise_failures() |
| else: |
| all_results = [api.fuchsia.test( |
| build=build, |
| test_pool=test_pool, |
| timeout_secs=test_timeout_secs, |
| pave=pave, |
| test_cmds=[ |
| 'runtests -o %s %s' % ( |
| api.fuchsia.results_dir_on_target, |
| runtests_args, |
| ), |
| ], |
| device_type=device_type, |
| external_network=networking_for_tests, |
| requires_secrets=requires_secrets, |
| )] |
| all_results[0] |
| |
| for test_results in all_results: |
| if upload_results: |
| test_results.upload_results( |
| gcs_bucket=gcs_bucket, |
| process_coverage_data='profile' in variants, |
| ) |
| test_results.raise_failures() |
| |
| |
| if run_host_tests: |
| test_results = api.fuchsia.test_on_host(build) |
| test_results.raise_failures() |
| |
| def GenTests(api): |
| # Tests using the defaults provided by fuchsia.test(). |
| yield api.fuchsia.test('default', properties={}) |
| yield api.fuchsia.test('cq', tryjob=True, properties={}) |
| |
| # Test cases for running tests. |
| yield api.fuchsia.test('isolated_tests', properties=dict(run_tests=True)) |
| yield api.fuchsia.test( |
| 'device_tests', |
| properties=dict( |
| run_tests=True, |
| device_type='Intel NUC Kit NUC7i5DNHE', |
| )) |
| yield api.fuchsia.test( |
| 'host_tests', |
| properties=dict( |
| ninja_targets=['build/gn:host_tests'], |
| run_host_tests=True, |
| )) |
| yield api.fuchsia.test( |
| 'host_and_device_tests', |
| properties=dict( |
| run_tests=True, |
| device_type='Intel NUC Kit NUC7i5DNHE', |
| run_host_tests=True, |
| )) |
| |
| # Test cases for tests with networking. |
| yield api.fuchsia.test( |
| 'isolated_tests_with_networking', |
| properties=dict( |
| run_tests=True, |
| networking_for_tests=True, |
| )) |
| yield api.fuchsia.test( |
| 'device_tests_with_networking', |
| # Networking is only supported for QEMU, so this test should fail. |
| expect_failure=True, |
| clear_default_steps=True, |
| properties=dict( |
| run_tests=True, |
| networking_for_tests=True, |
| device_type='Intel NUC Kit NUC7i5DNHE', |
| ), |
| ) |
| yield api.fuchsia.test( |
| 'cq_with_networking', |
| tryjob=True, |
| # Networking is not supported for tryjobs, so this test should fail. |
| expect_failure=True, |
| clear_default_steps=True, |
| properties=dict( |
| run_tests=True, |
| networking_for_tests=True, |
| ), |
| ) |
| |
| # Test cases for checking out Fuchsia from a snapshot. |
| yield api.fuchsia.test( |
| 'checkout_from_snapshot', |
| clear_default_properties=True, |
| properties=dict( |
| checkout_snapshot=True, |
| repository='https://fuchsia.googlesource.com/snapshots', |
| revision='69acf9677ff075e15329cc860d968c1f70be5e6a', |
| target='x64', |
| packages=['topaz/packages/default'], |
| ), |
| ) |
| yield api.fuchsia.test( |
| 'cq_checkout_from_snapshot', |
| clear_default_properties=True, |
| tryjob=True, |
| properties=dict( |
| checkout_snapshot=True, |
| target='x64', |
| packages=['topaz/packages/default'], |
| ), |
| ) |
| |
| # Test the 'vendor/x' case of verifying build packages. |
| # The non-vendor case is tested by most other tests. |
| yield api.fuchsia.test( |
| 'build-packages-vendor', |
| properties=dict(repo='vendor/foobar'), |
| ) |
| |
| # Test cases for generating symbol files as part of the build |
| yield api.fuchsia.test( |
| 'include_breakpad_symbols', |
| properties=dict( |
| # build_type and target determine the path used in the key of |
| # fuchsia.breakpad_symbol_summary below. |
| build_type='release', |
| target='x64', |
| ninja_targets=['build/gn:breakpad_symbols'], |
| include_breakpad_symbols=True, |
| ), |
| ) |
| yield api.fuchsia.test( |
| 'include_symbol_archive', |
| properties=dict( |
| build_type='release', |
| target='x64', |
| include_symbol_archive=True, |
| ), |
| ) |
| |
| # Test cases for exercising the secrets pipeline. |
| yield api.fuchsia.test( |
| 'ci_requires_secrets', |
| tryjob=False, |
| properties=dict( |
| requires_secrets=True, |
| networking_for_tests=True, |
| run_tests=True, |
| device_type='QEMU', |
| ), |
| ) |
| yield api.fuchsia.test( |
| 'cq_requires_secrets', |
| tryjob=True, |
| # Secrets are not supported for tryjobs. |
| expect_failure=True, |
| clear_default_steps=True, |
| properties=dict( |
| requires_secrets=True, |
| networking_for_tests=True, |
| run_tests=True, |
| device_type='QEMU', |
| ), |
| ) |
| yield api.fuchsia.test( |
| 'ci_requires_secrets_no_networking', |
| tryjob=False, |
| # Secrets are not supported without networking. |
| expect_failure=True, |
| clear_default_steps=True, |
| properties=dict( |
| requires_secrets=True, |
| networking_for_tests=False, |
| run_tests=True, |
| device_type='QEMU', |
| ), |
| ) |
| yield api.fuchsia.test( |
| 'ci_requires_secrets_on_hardware', |
| tryjob=False, |
| # Secrets are not supported on hardware. |
| expect_failure=True, |
| clear_default_steps=True, |
| properties=dict( |
| requires_secrets=True, |
| networking_for_tests=True, |
| run_tests=True, |
| device_type='Intel NUC Kit NUC7i5DNHE', |
| ), |
| ) |
| |
| # Test cases for testing in shards. |
| yield api.fuchsia.test( |
| 'test_in_shards', |
| clear_default_steps=True, |
| properties=dict( |
| run_tests=True, |
| test_in_shards=True, |
| ), |
| steps=[ |
| api.fuchsia.shards_step_data(shards=[ |
| api.testsharder.shard( |
| name='fuchsia-0000', |
| tests=[api.testsharder.test( |
| name='test0', |
| location='/path/to/test0', |
| )], |
| device_type='QEMU', |
| ), |
| api.testsharder.shard( |
| name='fuchsia-0001', |
| tests=[api.testsharder.test( |
| name='test1', |
| location='/path/to/test1', |
| )], |
| device_type='NUC', |
| ), |
| ]), |
| api.fuchsia.tasks_step_data( |
| api.fuchsia.task_mock_data(id='610', name='fuchsia-0000'), |
| api.fuchsia.task_mock_data(id='710', name='fuchsia-0001'), |
| ), |
| api.fuchsia.test_step_data(shard_name='fuchsia-0000'), |
| api.fuchsia.test_step_data(shard_name='fuchsia-0001'), |
| ]) |