| # Copyright 2023 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Checks GN args specified in starlark versus mb config file. |
| |
| Once migration of all GN args to starlark is complete, this recipe will be |
| irrelevant. |
| """ |
| |
| from collections.abc import Iterable |
| import contextlib |
| import difflib |
| import re |
| import typing |
| |
| from recipe_engine import config_types |
| from recipe_engine import post_process |
| from recipe_engine import recipe_test_api |
| from recipe_engine.engine_types import StepPresentation |
| |
| from RECIPE_MODULES.build import proto_validation |
| from RECIPE_MODULES.build.attr_utils import attrib, attrs |
| from RECIPE_MODULES.build.chromium_types import BuilderId |
| |
| from PB.go.chromium.org.luci.buildbucket.proto import common as common_pb |
| from PB.recipe_engine import result as result_pb |
| from PB.recipes.build.chromium import gn_args_verifier as gn_args_verifier_pb |
| |
| PROPERTIES = gn_args_verifier_pb.InputProperties |
| |
| from dataclasses import dataclass |
| |
| from recipe_engine.recipe_api import RecipeScriptApi |
| from recipe_engine.recipe_test_api import RecipeTestApi |
| |
| from RECIPE_MODULES.build import chromium, gn |
| from RECIPE_MODULES.depot_tools import bot_update, gclient, tryserver |
| from RECIPE_MODULES.recipe_engine import ( |
| buildbucket, |
| context, |
| file, |
| json, |
| path, |
| properties, |
| raw_io, |
| step, |
| ) |
| |
| |
| @dataclass |
| class DEPS(RecipeScriptApi): |
| bot_update: bot_update.API |
| buildbucket: buildbucket.API |
| chromium: chromium.API |
| context: context.API |
| file: file.API |
| gclient: gclient.API |
| gn: gn.API |
| json: json.API |
| path: path.API |
| properties: properties.API |
| raw_io: raw_io.API |
| step: step.API |
| tryserver: tryserver.API |
| |
| |
| @dataclass |
| class TEST_DEPS(RecipeTestApi): |
| buildbucket: buildbucket.TEST_API |
| chromium: chromium.TEST_API |
| file: file.TEST_API |
| path: path.TEST_API |
| properties: properties.TEST_API |
| raw_io: raw_io.TEST_API |
| step: step.TEST_API |
| tryserver: tryserver.TEST_API |
| |
| |
| def _gn_args_file_re(builder_config_directory: str) -> re.Pattern: |
| """Get a regular expression that will match a GN args file path. |
| |
| Returns: |
| A pattern object that will match repo-root-relative paths that refer |
| to GN args files. The pattern captures the repo-root-relative |
| path to the directory that contains the gn-args.json of a single builder. |
| """ |
| return re.compile( |
| f'^{re.escape(builder_config_directory)}/([^/]+/[^/]+/gn-args\.json)' |
| ) |
| |
| |
| def RunSteps(api: DEPS, properties): |
| api.tryserver.require_is_tryserver() |
| |
| errors = VALIDATORS.validate(properties) |
| if errors: |
| return _result( |
| status=common_pb.INFRA_FAILURE, |
| elements=errors, |
| header='The following errors were found with the input properties:', |
| ) |
| |
| api.gclient.set_config(properties.gclient_config) |
| api.chromium.set_config('chromium') |
| |
| with api.context(cwd=api.path.cache_dir / 'builder'): |
| update_result = api.bot_update.ensure_checkout(patch=True) |
| checkout_dir = update_result.checkout_dir |
| source_dir = update_result.source_root.path |
| patch_dir = update_result.patch_root.path |
| |
| # Absolute path of the per-builder config directory root. |
| builder_config_root = patch_dir / properties.builder_config_directory |
| |
| with api.step.nest('process data from patch'): |
| gn_args_file_path_by_builder_by_builder_group = api.file.read_json( |
| 'read gn_args_locations.json', |
| builder_config_root / 'gn_args_locations.json', |
| ) |
| # Mapping from gn-args.json file path to the builder group of the builder. |
| builder_group_by_gn_args_file_path = {} |
| for ( |
| builder_group, |
| gn_args_file_path_by_builder, |
| ) in gn_args_file_path_by_builder_by_builder_group.items(): |
| for file_path in gn_args_file_path_by_builder.values(): |
| builder_group_by_gn_args_file_path[file_path] = builder_group |
| |
| with api.context(cwd=patch_dir): |
| affected_files = set(api.tryserver.get_files_affected_by_patch('')) |
| with api.step.nest('parse gn-args.json files'): |
| # Mapping from per-builder gn-args.json to its content. |
| gn_args_json_by_file_path = {} |
| gn_args_file_re = _gn_args_file_re(properties.builder_config_directory) |
| for f in affected_files: |
| if match := gn_args_file_re.match(f): |
| file_path = match.group(1) |
| # Add a GN args file to the mapping for verification if it is not |
| # being removed by the patch. |
| if file_path in builder_group_by_gn_args_file_path: |
| gn_args_json_by_file_path[file_path] = api.file.read_json( |
| f'read {file_path}', patch_dir / f |
| ) |
| |
| failures = [] |
| |
| gn_args_by_phase_by_builder_id: dict[BuilderId, _GnArgsByPhase] = {} |
| file_path_by_builder_id = {} |
| for file_path, gn_args_json in gn_args_json_by_file_path.items(): |
| builder_id = BuilderId.create_for_group( |
| builder_group_by_gn_args_file_path[file_path], file_path.split('/')[1] |
| ) |
| file_path_by_builder_id[builder_id] = file_path |
| |
| gn_args_by_phase = _get_post_patch_gn_args( |
| api, |
| checkout_dir, |
| source_dir, |
| file_path, |
| builder_id, |
| gn_args_json, |
| properties.mb_config_paths, |
| ) |
| if gn_args_by_phase is not None: |
| gn_args_by_phase_by_builder_id[builder_id] = gn_args_by_phase |
| else: |
| failures.append(str(builder_id)) |
| |
| if failures: |
| return _result( |
| status=common_pb.FAILURE, |
| elements=list(failures), |
| header=( |
| 'could not get GN args with patch applied for the following builders:' |
| ), |
| ) |
| |
| with api.context(cwd=checkout_dir): |
| api.bot_update.deapply_patch(update_result) |
| |
| # Verify each builder. |
| failures = [] |
| |
| for builder_id, gn_args_by_phase in gn_args_by_phase_by_builder_id.items(): |
| file_path = file_path_by_builder_id[builder_id] |
| step_name = f'verify {file_path}' |
| |
| if api.path.exists(builder_config_root / file_path): |
| api.step.empty( |
| name=step_name, step_text='builder already migrated, skip.' |
| ) |
| continue |
| |
| success = _verify_gn_args( |
| api, |
| checkout_dir, |
| source_dir, |
| step_name, |
| builder_id, |
| gn_args_by_phase, |
| properties.mb_config_paths, |
| ) |
| |
| if not success: |
| failures.append(str(builder_id)) |
| |
| if failures: |
| return _result( |
| status=common_pb.FAILURE, |
| elements=list(failures), |
| header='verification failed for the following builders:', |
| ) |
| |
| |
| class _GnArgsJson(typing.TypedDict, total=False): |
| phases: dict[str, typing.Any] |
| |
| |
| # For non-phased builders, there will only be a single entry with None as the |
| # key |
| _GnArgsByPhase = dict[str | None, str] |
| |
| |
| def _get_post_patch_gn_args( |
| api: DEPS, |
| checkout_dir: config_types.Path, |
| source_dir: config_types.Path, |
| step_name: str, |
| builder_id: BuilderId, |
| gn_args_json: _GnArgsJson, |
| mb_config_paths: list[str], |
| ) -> _GnArgsByPhase | None: |
| """Get a builder's GN args with the patch applied. |
| |
| Args: |
| api: The recipe API object. |
| checkout_dir: The directory where the checkout was performed. The source |
| should already be checked out, with the patch from the CL applied. |
| step_name: The name of the step to use for getting the GN args. |
| source_dir: The path to the top level repo. |
| builder_id: The ID of the builder to get GN args for. |
| gn_args_json: The json decoded contents of the builder's gn_args.json file. |
| mb_config_paths: The paths to the MB config files to try to look up the |
| builder's GN args in. The paths are relative to checkout_dir. It is an |
| error if the builder isn't present in any of the config files. |
| |
| Returns: |
| The GN args for each phase of the builder or None if the GN args couldn't be |
| looked up for the builder (the builder isn't present in any of the config |
| files or some other error occurs running mb lookup). |
| """ |
| with api.step.nest(step_name) as presentation: |
| phases = gn_args_json.get('phases') or [None] |
| |
| for mb_config_path in mb_config_paths: |
| with api.step.nest(f'check in {mb_config_path}'): |
| gn_args_by_phase = {} |
| |
| for phase in phases: |
| suffix = '' if phase is None else f', phase: {phase}' |
| try: |
| gn_args = _mb_lookup( |
| api, |
| source_dir, |
| f'mb lookup - {builder_id}{suffix}', |
| builder_id, |
| checkout_dir / mb_config_path, |
| phase, |
| ) |
| except api.step.StepFailure: |
| return None |
| |
| # If the builder isn't present in the config file, we don't need to |
| # check each phase |
| if gn_args is None: |
| presentation.step_text = f'{builder_id} is not present' |
| break |
| |
| gn_args_by_phase[phase] = gn_args |
| |
| else: |
| return gn_args_by_phase |
| |
| presentation.status = api.step.FAILURE |
| presentation.step_text = 'not present in any of the config files' |
| return None |
| |
| |
| def _verify_gn_args( |
| api: DEPS, |
| checkout_dir: config_types.Path, |
| source_dir: config_types.Path, |
| step_name: str, |
| builder_id: BuilderId, |
| post_patch_gn_args_by_phase: _GnArgsByPhase, |
| mb_config_paths: list[str], |
| ) -> bool: |
| """Get a builder's GN args with the patch applied. |
| |
| Args: |
| api: The recipe API object. |
| checkout_dir: The directory where the checkout was performed. The source |
| should already be checked out, without the patch from the CL applied. |
| step_name: The name of the step to use for the verification. |
| source_dir: The path to the top level repo. |
| builder_id: The ID of the builder to verify GN args for. |
| post_patch_gn_args_by_phase: The GN args for the builder with the patch |
| applied for each phase. |
| mb_config_paths: The paths to the MB config files to try to look up the |
| builder's GN args in. The paths are relative to checkout_dir. It is an |
| error if the builder isn't present in any of the config files. |
| |
| Returns: |
| True if the verification succeeds (the GN args for each phase are equal or |
| the builder doesn't exist in any of the config files). False if the |
| verifications fails (there is a difference in GN args) or some error occurs |
| when getting the GN args for the builder. |
| """ |
| with api.step.nest(step_name) as presentation: |
| for mb_config_path in mb_config_paths: |
| with api.step.nest(f'check in {mb_config_path}') as file_presentation: |
| success = True |
| |
| for phase, post_patch_args in post_patch_gn_args_by_phase.items(): |
| ctx = ( |
| contextlib.nullcontext(presentation) |
| if phase is None |
| else api.step.nest(f'phase: {phase}') |
| ) |
| with ctx as phase_presentation: |
| try: |
| pre_patch_args = _mb_lookup( |
| api, |
| source_dir, |
| 'mb lookup', |
| builder_id, |
| checkout_dir / mb_config_path, |
| phase, |
| ) |
| except api.step.StepFailure: |
| return False |
| |
| # If the builder isn't present in the config file, we don't need to |
| # check each phase |
| if pre_patch_args is None: |
| file_presentation.step_text = f'{builder_id} is not present' |
| break |
| |
| if not _compare_gn_args( |
| api, pre_patch_args, post_patch_args, phase_presentation |
| ): |
| success = False |
| |
| else: |
| return success |
| |
| presentation.step_text = "new builder: wasn't present in any config file" |
| return True |
| |
| |
| def _compare_gn_args( |
| api: DEPS, |
| pre_patch_args: str, |
| post_patch_args: str, |
| presentation: StepPresentation, |
| ) -> bool: |
| pre_patch_args_lines = pre_patch_args.splitlines() |
| post_patch_args_lines = post_patch_args.splitlines() |
| diff = list( |
| difflib.unified_diff( |
| pre_patch_args_lines, |
| post_patch_args_lines, |
| fromfile='mb config GN args', |
| tofile='Starlark GN args', |
| n=max(len(pre_patch_args_lines), len(post_patch_args_lines)), |
| ) |
| ) |
| |
| if diff: |
| for line in diff[4:]: |
| if not _check_diff_line(line): |
| presentation.logs['diff'] = diff |
| presentation.status = api.step.FAILURE |
| presentation.step_text = 'GN args mismatch; see "diff" log for details' |
| return False |
| |
| presentation.step_text = 'GN args match between starlark and mb config' |
| return True |
| |
| |
| def _check_diff_line(line: str) -> bool: |
| """Checks if a gn_arg unified diff line is an acceptable change. |
| |
| Because mb_config.pyl did not require configs to explicitly list target_cpu |
| and target_os and starlark requires those args to be explicit, a properly |
| migrated list may not match exactly. This function checks lines of the |
| unified diff of the gn args, and will verify that a line represents an exact |
| match of gn args or that the only difference is the addition of target_os |
| or target_cpu when those args did not exist in the mb_config.pyl file. |
| |
| Args: |
| line: the unified diff line to check for gn_args mismatches. |
| |
| Returns: |
| True if a line represents an exact match of args or if the line represents |
| the addition of target_cpu or target_os when those args were not present |
| in the original mb_config file. |
| False in all other cases, including when target_cpu and target_os existed in |
| the mb_config file and have been changed in the starlark file. |
| """ |
| # Because starlark requires target_cpu and target_os, adding those args when |
| # they did not exist before is fine. |
| if line.startswith(('+target_cpu', '+target_os')): |
| return True |
| |
| # Lines that do not start with + or - represent a match so those are fine. |
| if not line.startswith(('+', '-')): |
| return True |
| |
| # Otherwise a non permitted mismatch. |
| return False |
| |
| |
| _UNKNOWN_BUILDER_RETCODE = 2 |
| |
| |
| def _mb_lookup( |
| api: DEPS, |
| source_dir: config_types.Path, |
| step_name: str, |
| builder_id: BuilderId, |
| mb_config_path: config_types.Path, |
| phase: str | None, |
| ) -> str | None: |
| """Lookup the GN args for a builder. |
| |
| Args: |
| api: The recipe API object. |
| step_name: The name of the step to use for the lookup. |
| builder_id: The ID of the builder to get GN args for. |
| mb_config_paths: The path to the MB config file to lookup the builder's GN |
| args in. |
| phase: The phase of the GN args to look up. |
| |
| Returns: |
| The GN args as returned by "mb lookup" if the builder is present in |
| the config file. None if the builder is not present in the config |
| file. |
| |
| Raises: |
| StepFailure if some error occurs executing the lookup besides the builder |
| not being present in the config file. |
| """ |
| result = api.chromium.run_mb_cmd( |
| step_name, |
| 'lookup', |
| source_dir, |
| builder_id, |
| mb_config_path=mb_config_path, |
| phase=phase, |
| additional_args=['--quiet'], |
| ok_ret=(0, _UNKNOWN_BUILDER_RETCODE), |
| stdout=api.raw_io.output_text(add_output_log='on_failure'), |
| ) |
| if result.retcode == _UNKNOWN_BUILDER_RETCODE: |
| return None |
| gn_args = result.presentation.step_text = f'\n{result.stdout}' |
| return gn_args |
| |
| |
| VALIDATORS = proto_validation.Registry() |
| |
| |
| @VALIDATORS.register(gn_args_verifier_pb.InputProperties) |
| def _validate_properties(message, ctx): |
| ctx.validate_field(message, 'gclient_config') |
| ctx.validate_field(message, 'builder_config_directory') |
| ctx.validate_repeated_field(message, 'mb_config_paths') |
| |
| |
| def _result( |
| status: common_pb.Status, |
| header: str, |
| elements: Iterable[str], |
| ) -> result_pb.RawResult: |
| summary = [header, ''] |
| summary.extend('* {}'.format(e) for e in elements) |
| return result_pb.RawResult(status=status, summary_markdown='\n'.join(summary)) |
| |
| |
| def GenTests(api: TEST_DEPS): |
| |
| builder_config_dir = 'builder-config-dir' |
| |
| @attrs() |
| class BuilderDataEntry: |
| """An object that contains test data for an individual builder.""" |
| |
| # Builder group name |
| group = attrib(str) |
| |
| # Bucket name |
| bucket = attrib(str) |
| |
| # Builder name |
| builder = attrib(str) |
| |
| # The mb config file that the builder should be present in, as a path |
| # relative to the directory where the checkout would be performed |
| mb_config_path = attrib(str, default=None) |
| |
| # Content of gn-args.json generated by Starlark definition. |
| sl_gn_args = attrib(dict, default={'gn_args': {}}) |
| |
| # GN args defined in mb config file, but stored in the same format as |
| # the sl_gn_args dict. |
| mb_gn_args = attrib(dict, default={'gn_args': {}}) |
| |
| # Whether the builder is being removed in the patch. |
| builder_removed = attrib(bool, default=False) |
| |
| # Whether the builder is being added in the patch |
| builder_added = attrib(bool, default=False) |
| |
| # Whether the builder has already been migrated to Starlark GN args |
| already_migrated = attrib(bool, default=False) |
| |
| # Whether the mb_lookup step will fail with patch applied |
| mb_lookup_error_with_patch = attrib(bool, default=False) |
| |
| # Whether the mb_lookup step will fail without patch applied |
| mb_lookup_error_without_patch = attrib(bool, default=False) |
| |
| def gn_args_test_data( |
| builder_entries: Iterable[BuilderDataEntry], |
| *, |
| mb_config_paths: list[str] | None = None, |
| ) -> recipe_test_api.StepTestData: |
| """Set up test data for GN args verification steps |
| |
| Args: |
| * builder_entries - A list of BuilderDataEntry objects. |
| """ |
| |
| def dict_to_gn_args_str(args: dict) -> str: |
| """Convert a dict of GN args into an args.gn format string.""" |
| lines = [] |
| for arg, val in args.items(): |
| str_val = str(val) |
| if isinstance(val, str): |
| str_val = f'"{str_val}"' |
| elif isinstance(val, bool): |
| str_val = str_val.lower() |
| lines.append(f'{arg} = {str_val}') |
| return '\n'.join(lines) |
| |
| if mb_config_paths is None: |
| mb_config_paths = list( |
| { |
| e.mb_config_path: None |
| for e in builder_entries |
| if e.mb_config_path is not None |
| } |
| ) |
| |
| data = api.properties( |
| gn_args_verifier_pb.InputProperties( |
| gclient_config='chromium', |
| builder_config_directory=builder_config_dir, |
| mb_config_paths=mb_config_paths, |
| ) |
| ) |
| |
| locations_json = {} |
| gn_args_json_paths = [] |
| for builder_data in builder_entries: |
| group = builder_data.group |
| bucket = builder_data.bucket |
| builder = builder_data.builder |
| file_path = f'{bucket}/{builder}/gn-args.json' |
| path_in_repo = f'{builder_config_dir}/{file_path}' |
| gn_args_json_paths.append(path_in_repo) |
| if builder_data.builder_removed: |
| continue |
| |
| locations_json.setdefault(group, {})[builder] = file_path |
| |
| # Setup Starlark GN args test data |
| sl_gn_args = builder_data.sl_gn_args |
| |
| data += api.step_data( |
| f'process data from patch.parse gn-args.json files.read {file_path}', |
| api.file.read_json(sl_gn_args), |
| ) |
| |
| if 'phases' in sl_gn_args: |
| sl_gn_args_and_suffixes = [ |
| (value['gn_args'], f', phase: {phase}') |
| for phase, value in sl_gn_args['phases'].items() |
| ] |
| else: |
| sl_gn_args_and_suffixes = [(sl_gn_args['gn_args'], '')] |
| |
| for mb_config_path in mb_config_paths: |
| builder_in_config_file = mb_config_path == builder_data.mb_config_path |
| for gn_args, step_name in sl_gn_args_and_suffixes: |
| step_name = ( |
| f'process data from patch.{bucket}/{builder}/gn-args.json' |
| f'.check in {mb_config_path}' |
| f'.mb lookup - {group}:{builder}{step_name}' |
| ) |
| # We'll only check a single phase for a builder that's not in the |
| # config file |
| if not builder_in_config_file: |
| data += api.step_data(step_name, retcode=_UNKNOWN_BUILDER_RETCODE) |
| break |
| if builder_data.mb_lookup_error_with_patch: |
| data += api.step_data(step_name, retcode=1) |
| break |
| data += api.step_data( |
| step_name, |
| stdout=api.raw_io.output_text(dict_to_gn_args_str(gn_args)), |
| ) |
| # Once the config file containing the builder is found, we don't check |
| # remaining files |
| if builder_in_config_file: |
| break |
| |
| # Verification doesn't happen if there was an error when looking up GN |
| # args or if the builder was never found in any config file |
| if ( |
| builder_data.mb_lookup_error_with_patch |
| or builder_data.mb_config_path not in mb_config_paths |
| ): |
| continue |
| |
| # Setup mb config GN args test data |
| if builder_data.already_migrated: |
| data += api.path.exists( |
| api.path.cache_dir / 'builder/src' / path_in_repo |
| ) |
| data += api.post_process( |
| post_process.StepTextEquals, |
| f'verify {bucket}/{builder}/gn-args.json', |
| 'builder already migrated, skip.', |
| ) |
| continue |
| |
| mb_gn_args = builder_data.mb_gn_args |
| if 'phases' in mb_gn_args: |
| mb_gn_args_and_phase_steps = [ |
| (value['gn_args'], f'.phase: {phase}') |
| for phase, value in mb_gn_args['phases'].items() |
| ] |
| else: |
| mb_gn_args_and_phase_steps = [(mb_gn_args['gn_args'], '')] |
| |
| for mb_config_path in mb_config_paths: |
| builder_in_config_file = ( |
| mb_config_path == builder_data.mb_config_path |
| and not builder_data.builder_added |
| ) |
| for gn_args, phase_step in mb_gn_args_and_phase_steps: |
| step_name = ( |
| f'verify {bucket}/{builder}/gn-args.json' |
| f'.check in {mb_config_path}' |
| f'{phase_step}.mb lookup' |
| ) |
| # We'll only check a single phase for a builder that's not in the |
| # config file |
| if not builder_in_config_file: |
| data += api.step_data(step_name, retcode=_UNKNOWN_BUILDER_RETCODE) |
| break |
| if builder_data.mb_lookup_error_without_patch: |
| data += api.step_data(step_name, retcode=1) |
| break |
| data += api.step_data( |
| step_name, |
| stdout=api.raw_io.output_text(dict_to_gn_args_str(gn_args)), |
| ) |
| # Once the config file containing the builder is found, we don't check |
| # remaining files |
| if builder_in_config_file: |
| break |
| |
| data += api.tryserver.get_files_affected_by_patch( |
| [*gn_args_json_paths], |
| step_name=('process data from patch.git diff to analyze patch'), |
| ) |
| data += api.step_data( |
| 'process data from patch.read gn_args_locations.json', |
| api.file.read_json(locations_json), |
| ) |
| return data |
| |
| yield api.test( |
| 'basic', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry( |
| group='group1', |
| bucket='bucket1', |
| builder='builder1', |
| mb_config_path='src/tools/mb/mb_config.pyl', |
| sl_gn_args={ |
| 'gn_args': { |
| 'str_arg': 'test1', |
| 'int_arg': 1, |
| 'bool_arg': False, |
| # Adding target_cpu and/or target_os when that arg did not |
| # exist before should be considered a match since starlark |
| # requires those to be explicitly set. |
| 'target_cpu': 'CPU', |
| 'target_os': 'OS', |
| } |
| }, |
| mb_gn_args={ |
| 'gn_args': {'str_arg': 'test1', 'int_arg': 1, 'bool_arg': False} |
| }, |
| ), |
| BuilderDataEntry( |
| group='group2', |
| bucket='bucket2', |
| builder='builder2', |
| mb_config_path='src/internal/tools/mb/mb_config.pyl', |
| sl_gn_args={ |
| 'phases': { |
| 'phase1': { |
| 'gn_args': { |
| 'is_phase1': True, |
| } |
| }, |
| 'phase2': { |
| 'gn_args': { |
| 'is_phase2': True, |
| } |
| }, |
| } |
| }, |
| mb_gn_args={ |
| 'phases': { |
| 'phase1': { |
| 'gn_args': { |
| 'is_phase1': True, |
| } |
| }, |
| 'phase2': { |
| 'gn_args': { |
| 'is_phase2': True, |
| } |
| }, |
| } |
| }, |
| ), |
| ] |
| ), |
| api.post_process(post_process.DropExpectation), |
| status='SUCCESS', |
| ) |
| |
| yield api.test( |
| 'gn_args_mismatch', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry( |
| group='group1', |
| bucket='bucket1', |
| builder='builder1', |
| mb_config_path='src/tools/mb/mb_config.pyl', |
| sl_gn_args={ |
| 'gn_args': { |
| 'str_arg': 'test1', |
| 'int_arg': 1, |
| 'bool_arg': False, |
| # Overwriting an existing target_cpu or target_os should |
| # be considered a gn args mismatch. |
| 'target_cpu': 'cpu2', |
| 'target_os': 'os2', |
| } |
| }, |
| mb_gn_args={ |
| 'gn_args': { |
| 'str_arg': 'test1', |
| 'int_arg': 2, |
| 'bool_arg': False, |
| 'target_cpu': 'cpu1', |
| 'target_os': 'os1', |
| } |
| }, |
| ), |
| BuilderDataEntry( |
| group='group2', |
| bucket='bucket2', |
| builder='builder2', |
| mb_config_path='src/internal/tools/mb/mb_config.pyl', |
| sl_gn_args={ |
| 'phases': { |
| 'phase1': { |
| 'gn_args': { |
| 'is_phase1': True, |
| } |
| }, |
| 'phase2': { |
| 'gn_args': { |
| 'is_phase2': True, |
| } |
| }, |
| } |
| }, |
| mb_gn_args={ |
| 'phases': { |
| 'phase1': { |
| 'gn_args': { |
| 'is_phase1': False, |
| } |
| }, |
| 'phase2': { |
| 'gn_args': { |
| 'int_arg': 2, |
| } |
| }, |
| } |
| }, |
| ), |
| ] |
| ), |
| api.post_process( |
| post_process.StepFailure, 'verify bucket1/builder1/gn-args.json' |
| ), |
| api.post_process( |
| post_process.StepFailure, 'verify bucket2/builder2/gn-args.json' |
| ), |
| api.post_process(post_process.DropExpectation), |
| status='FAILURE', |
| ) |
| |
| yield api.test( |
| 'already_migrated_builder', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry( |
| group='group1', |
| bucket='bucket1', |
| builder='builder1', |
| mb_config_path='src/tools/mb/mb_config.pyl', |
| sl_gn_args={ |
| 'gn_args': {'str_arg': 'test1', 'int_arg': 1, 'bool_arg': False} |
| }, |
| already_migrated=True, |
| ) |
| ] |
| ), |
| api.post_process(post_process.DropExpectation), |
| status='SUCCESS', |
| ) |
| |
| yield api.test( |
| 'builder-not-in-any-config-files', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry(group='group1', bucket='bucket1', builder='builder1'), |
| ], |
| mb_config_paths=['src/tools/mb/mb_config.pyl'], |
| ), |
| api.post_check(post_process.DoesNotRun, 'bot_update (without patch)'), |
| api.post_process(post_process.DropExpectation), |
| status='FAILURE', |
| ) |
| |
| yield api.test( |
| 'mb-lookup-error-with-patch', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry( |
| group='group1', |
| bucket='bucket1', |
| builder='builder1', |
| mb_config_path='src/tools/mb/mb_config.pyl', |
| mb_lookup_error_with_patch=True, |
| ), |
| ] |
| ), |
| api.post_check(post_process.DoesNotRun, 'bot_update (without patch)'), |
| api.post_process(post_process.DropExpectation), |
| status='FAILURE', |
| ) |
| |
| yield api.test( |
| 'mb-lookup-error-without-patch', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry( |
| group='group1', |
| bucket='bucket1', |
| builder='builder1', |
| mb_config_path='src/tools/mb/mb_config.pyl', |
| sl_gn_args={'gn_args': {}}, |
| mb_lookup_error_without_patch=True, |
| ), |
| ] |
| ), |
| api.post_process(post_process.DropExpectation), |
| status='FAILURE', |
| ) |
| |
| yield api.test( |
| 'added-builder', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry( |
| group='group1', |
| bucket='bucket1', |
| builder='builder1', |
| mb_config_path='src/tools/mb/mb_config.pyl', |
| builder_added=True, |
| ) |
| ] |
| ), |
| api.post_check( |
| post_process.StepTextEquals, |
| 'verify bucket1/builder1/gn-args.json', |
| "new builder: wasn't present in any config file", |
| ), |
| api.post_process(post_process.DropExpectation), |
| status='SUCCESS', |
| ) |
| |
| yield api.test( |
| 'removed-builder', |
| api.buildbucket.try_build(), |
| gn_args_test_data( |
| [ |
| BuilderDataEntry( |
| group='group1', |
| bucket='bucket1', |
| builder='builder1', |
| builder_removed=True, |
| ) |
| ], |
| mb_config_paths=['src/tools/mb/mb_config.pyl'], |
| ), |
| api.post_process(post_process.DropExpectation), |
| status='SUCCESS', |
| ) |
| |
| # Must appear last since it drops expectations |
| def invalid_properties(*errors): |
| test_data = api.expect_status('INFRA_FAILURE') |
| test_data += api.post_check( |
| post_process.SummaryMarkdownRE, |
| '^The following errors were found with the input properties', |
| ) |
| for error in errors: |
| test_data += api.post_check(post_process.SummaryMarkdownRE, error) |
| test_data += api.post_process(post_process.DropExpectation) |
| return test_data |
| |
| yield api.test( |
| 'gclient-config-not-set', |
| api.buildbucket.try_build(), |
| invalid_properties('gclient_config is not set'), |
| ) |
| |
| yield api.test( |
| 'builder-config-dir-not-set', |
| api.buildbucket.try_build(), |
| invalid_properties('builder_config_directory is not set'), |
| ) |
| |
| yield api.test( |
| 'mb-config-paths-not-set', |
| api.buildbucket.try_build(), |
| invalid_properties('mb_config_paths is empty'), |
| ) |