def RunSteps(api): # TODO(martinis) change this # The api.step object is directly callable. api.step('hello', ['echo', 'Hello World']) api.step('hello', ['echo', 'Why hello, there.']) # You can also manipulate various aspects of the step, such as env. # These are passed straight through to subprocess.Popen. # Also, abusing bash -c in this way is a TERRIBLE IDEA DON'T DO IT. api.step('goodbye', ['bash', '-c', 'echo Good bye, $friend.'], env={'friend': 'Darth Vader'}) # Finally, you can make your step accept any return code api.step('anything is cool', ['bash', '-c', 'exit 3'], ok_ret='any') # We can manipulate the step presentation arbitrarily until we run # the next step. step_result = api.step('hello', ['echo', 'hello']) step_result.presentation.status = api.step.EXCEPTION try: api.step('goodbye', ['echo', 'goodbye']) # Modifying step_result now would raise an AssertionError. except api.step.StepFailure: # Raising anything besides StepFailure or StepWarning causes the build to go # purple. raise ValueError('goodbye must exit 0!') try: api.step('warning', ['echo', 'warning']) except api.step.StepFailure as e: e.result.presentation.status = api.step.WARNING raise api.step.StepWarning(e.message) # Aggregate failures from tests! try: with recipe_api.defer_results(): api.step('testa', ['echo', 'testa']) api.step('testb', ['echo', 'testb']) except recipe_api.AggregatedStepFailure as f: raise api.step.StepFailure("You can catch step failures.") # Some steps are needed from an infrastructure point of view. If these # steps fail, the build stops, but doesn't get turned red because it's # not the developers' fault. try: api.step('cleanup', ['echo', 'cleaning', 'up', 'build'], infra_step=True) except api.step.InfraFailure as f: assert f.result.presentation.status == api.step.EXCEPTION # Run a step through a made-up wrapper program. api.step('application', ['echo', 'main', 'application'], wrapper=['python', '-c', 'import sys; print sys.argv']) if api.properties.get('access_invalid_data'): result = api.step('no-op', ['echo', 'I', 'do', 'nothing']) # Trying to access non-existent attributes on the result should raise. _ = result.json.output
def roll_projects(self, projects): """Attempts to roll each project from the provided list. If rolling any of the projects leads to failures, other projects are not affected. """ project_data = self.m.luci_config.get_projects() with self.m.tempfile.temp_dir('recipes') as recipes_dir: self.m.cipd.ensure(recipes_dir, { 'infra/recipes-py': 'latest', }) results = [] with recipe_api.defer_results(): for project in projects: with self.m.step.nest(str(project)): results.append(self._roll_project( project_data[project], recipes_dir)) # We need to unwrap |DeferredResult|s. results = [r.get_result() for r in results] # Failures to roll are OK as long as at least one of the repos is moving # forward. For example, with repos with following dependencies: # # A <- B # A, B <- C # # New commit in A repo will need to get rolled into B first. However, # it'd also appear as a candidate for C roll, leading to a failure there. if ROLL_FAILURE in results and ROLL_SUCCESS not in results: self.m.python.failing_step( 'roll result', 'manual intervention needed: automated roll attempt failed')
def roll_projects(self, projects): """Attempts to roll each project from the provided list. If rolling any of the projects leads to failures, other projects are not affected. """ project_data = self.m.luci_config.get_projects() with recipe_api.defer_results(): for project in projects: with self.m.step.nest(str(project)): self._roll_project(project_data[project])
def roll_projects(self, projects): """Attempts to roll each project from the provided list. If rolling any of the projects leads to failures, other projects are not affected. Args: projects: list of tuples of project_id (string): id as found in recipes.cfg. project_url (string): Git repository URL of the project. """ recipes_dir = self.m.path['cache'].join('builder', 'recipe_engine') self.m.file.rmtree('ensure recipe_dir gone', recipes_dir) self.m.file.ensure_directory('ensure builder cache dir exists', self.m.path['cache'].join('builder')) with self.m.context(cwd=self.m.path['cache'].join('builder')): # Git clone really wants to have cwd set to something other than None. self.m.git( 'clone', '--depth', '1', 'https://chromium.googlesource.com/infra/luci/recipes-py', recipes_dir, name='clone recipe engine') results = [] with recipe_api.defer_results(): for project_id, project_url in projects: with self.m.step.nest(str(project_id)): results.append( self._roll_project(project_id, project_url, recipes_dir)) # We need to unwrap |DeferredResult|s. results = [r.get_result() for r in results] # Failures to roll are OK as long as at least one of the repos is moving # forward. For example, with repos with following dependencies: # # A <- B # A, B <- C # # New commit in A repo will need to get rolled into B first. However, # it'd also appear as a candidate for C roll, leading to a failure there. if ROLL_FAILURE in results and ROLL_SUCCESS not in results: self.m.python.failing_step( 'roll result', 'manual intervention needed: automated roll attempt failed')
def RunSteps(api, bad_return, raise_infra_failure, access_invalid_data): if bad_return: return RETURN_SCHEMA.new(test_me='this should fail') # TODO(martinis) change this # The api.step object is directly callable. api.step('hello', ['echo', 'Hello World']) api.step('hello', ['echo', 'Why hello, there.']) # You can also manipulate various aspects of the step, such as env. # These are passed straight through to subprocess.Popen. # Also, abusing bash -c in this way is a TERRIBLE IDEA DON'T DO IT. api.step('goodbye', ['bash', '-c', 'echo Good bye, $friend.'], env={'friend': 'Darth Vader'}) # You can modify environment in terms of old environment. Environment # variables are substituted in for expressions of the form %(VARNAME)s. api.step('recipes help', ['recipes.py', '--help'], env={ 'PATH': api.path.pathsep.join( [str(api.step.package_resource()), '%(PATH)s']), }) # Finally, you can make your step accept any return code api.step('anything is cool', ['bash', '-c', 'exit 3'], ok_ret='any') # We can manipulate the step presentation arbitrarily until we run # the next step. step_result = api.step('hello', ['echo', 'hello']) step_result.presentation.status = api.step.EXCEPTION step_result.presentation.logs['the reason'] = ['The reason\nit failed'] # Without a command, a step can be used to present some data from the recipe. step_result = api.step('Just print stuff', cmd=None) step_result.presentation.logs['more'] = ['More stuff'] try: api.step('goodbye', ['echo', 'goodbye']) # Modifying step_result now would raise an AssertionError. except api.step.StepFailure: # Raising anything besides StepFailure or StepWarning causes the build to go # purple. raise ValueError('goodbye must exit 0!') try: api.step('warning', ['echo', 'warning']) except api.step.StepFailure as e: e.result.presentation.status = api.step.WARNING raise api.step.StepWarning(e.message) # Aggregate failures from tests! try: with recipe_api.defer_results(): api.step('testa', ['echo', 'testa']) api.step('testb', ['echo', 'testb']) except recipe_api.AggregatedStepFailure as f: raise api.step.StepFailure("You can catch step failures.") # Some steps are needed from an infrastructure point of view. If these # steps fail, the build stops, but doesn't get turned red because it's # not the developers' fault. try: api.step('cleanup', ['echo', 'cleaning', 'up', 'build'], infra_step=True) except api.step.InfraFailure as f: assert f.result.presentation.status == api.step.EXCEPTION # Run a step through a made-up wrapper program. api.step('application', ['echo', 'main', 'application'], wrapper=['python', '-c', 'import sys; print sys.argv']) if access_invalid_data: result = api.step('no-op', ['echo', 'I', 'do', 'nothing']) # Trying to access non-existent attributes on the result should raise. _ = result.json.output return RETURN_SCHEMA(test_me=3)
def RunSteps(api, properties, env_properties): # Collect memory/cpu/process before task execution. api.os_utils.collect_os_info() """Steps to checkout flutter engine and execute web tests.""" cache_root = api.path['cache'].join('builder') checkout = GetCheckoutPath(api) if properties.clobber: api.file.rmtree('Clobber cache', cache_root) api.file.rmtree('Clobber build output', checkout.join('out')) api.file.ensure_directory('Ensure checkout cache', cache_root) api.goma.ensure() dart_bin = checkout.join( 'third_party', 'dart', 'tools', 'sdks', 'dart-sdk', 'bin' ) android_home = checkout.join('third_party', 'android_tools', 'sdk') env = { 'GOMA_DIR': api.goma.goma_dir, 'ANDROID_HOME': str(android_home), 'CHROME_NO_SANDBOX': 'true', 'ENGINE_PATH': cache_root } env_prefixes = {'PATH': [dart_bin]} api.flutter_deps.certs(env, env_prefixes) # Checkout source code and build api.repo_util.engine_checkout(cache_root, env, env_prefixes) if api.platform.is_mac: api.web_util.clone_goldens_repo(checkout) with api.context(cwd=cache_root, env=env, env_prefixes=env_prefixes), api.depot_tools.on_path(): # Checks before building the engine. Only run on Linux. if api.platform.is_linux: api.json_util.validate_json(checkout.join('flutter', 'ci')) FormatAndDartTest(api) Lint(api) api.gclient.runhooks() target_name = 'host_debug_unopt' gn_flags = ['--unoptimized', '--full-dart-sdk'] # Mac needs to install xcode as part of the building process. additional_args = [] felt_cmd = [ checkout.join('out', target_name, 'dart-sdk', 'bin', 'dart'), 'dev/felt.dart' ] isolated_hash = '' builds = [] if api.platform.is_linux: RunGN(api, *gn_flags) Build(api, target_name) # Archieve the engine. Start the drones. Due to capacity limits we are # Only using the drones on the Linux for now. # Archive build directory into isolate. isolated_hash = Archive(api, target_name) # Schedule builds. # TODO(nurhan): Currently this recipe only shards Linux. The web drones # recipe is written in a way that it can also support sharding for # macOS and Windows OSes. When more resources are available or when # MWE or WWE builders start running more than 1 hour, also shard those # builders. builds = schedule_builds_on_linux(api, isolated_hash) elif api.platform.is_mac: with SetupXcode(api): RunGN(api, *gn_flags) Build(api, target_name) additional_args = ['--browser', 'ios-safari'] else: # Platform = windows. RunGN(api, *gn_flags) Build(api, target_name) if api.platform.is_win: felt_cmd = [ checkout.join( 'flutter', 'lib', 'web_ui', 'dev', 'felt_windows.bat' ) ] # Update dart packages and run tests. local_pub = checkout.join('out', target_name, 'dart-sdk', 'bin', 'pub') with api.context( cwd=checkout.join('flutter', 'web_sdk', 'web_engine_tester')): api.step('pub get in web_engine_tester', [local_pub, 'get']) with api.context(cwd=checkout.join('flutter', 'lib', 'web_ui')): api.step('pub get in web_engine_tester', [local_pub, 'get']) # TODO(nurhan): carry licenses to another shard when we have more # resources. felt_licenses = copy.deepcopy(felt_cmd) felt_licenses.append('check-licenses') api.step('felt licenses', felt_licenses) if api.platform.is_mac: additional_args_safari_desktop = ['--browser', 'safari'] felt_test_safari_desktop = copy.deepcopy(felt_cmd) felt_test_safari_desktop.append('test') felt_test_safari_desktop.extend(additional_args_safari_desktop) api.step('felt test safari desktop', felt_test_safari_desktop) if api.platform.is_linux: # TODO(nurhan): Web engine analysis can also be part of felt and used # in a shard. web_engine_analysis_cmd = [ checkout.join( 'flutter', 'lib', 'web_ui', 'dev', 'web_engine_analysis.sh' ), ] api.step('web engine analysis', web_engine_analysis_cmd) builds = api.shard_util.collect_builds(builds) api.display_util.display_builds( step_name='display builds', builds=builds, raise_on_failure=True, ) CleanUpProcesses(api) elif api.platform.is_mac: with SetupXcode(api): with recipe_api.defer_results(): felt_test = copy.deepcopy(felt_cmd) felt_test.append('test') felt_test.extend(additional_args) api.step('felt ios-safari test', felt_test) api.web_util.upload_failing_goldens(checkout, 'ios-safari') CleanUpProcesses(api) else: api.web_util.chrome(checkout) felt_test = copy.deepcopy(felt_cmd) felt_test.append('test') felt_test.extend(additional_args) api.step('felt test chrome', felt_test) CleanUpProcesses(api)
def RunSteps(api, bad_return, access_invalid_data, access_deep_invalid_data, assign_extra_junk, timeout): if timeout: # Timeout causes the recipe engine to raise an exception if your step takes # longer to run than you allow. Units are seconds. if timeout == 1: api.step('timeout', ['sleep', '20'], timeout=1) elif timeout == 2: try: api.step('caught timeout', ['sleep', '20'], timeout=1) except api.step.StepFailure: return # TODO(martinis) change this # The api.step object is directly callable. api.step('hello', ['echo', 'Hello World']) api.step('hello', ['echo', 'Why hello, there.']) # You can change the current working directory as well api.step('mk subdir', ['mkdir', '-p', 'something']) with api.context(cwd=api.path['start_dir'].join('something')): api.step('something', ['bash', '-c', 'echo Why hello, there, in a subdir.']) # By default, all steps run in 'start_dir', or the cwd of the recipe engine # when the recipe begins. Because of this, setting cwd to start_dir doesn't # show anything in particular in the expectations. with api.context(cwd=api.path['start_dir']): api.step('start_dir ignored', ['bash', '-c', 'echo what happen']) # You can also manipulate various aspects of the step, such as env. # These are passed straight through to subprocess.Popen. # Also, abusing bash -c in this way is a TERRIBLE IDEA DON'T DO IT. with api.context(env={'friend': 'Darth Vader'}): api.step('goodbye', ['bash', '-c', 'echo Good bye, $friend.']) # You can modify environment in terms of old environment. Environment # variables are substituted in for expressions of the form %(VARNAME)s. with api.context(env={ 'PATH': api.path.pathsep.join([str(api.step.repo_resource()), '%(PATH)s']) }): api.step('recipes help', ['recipes.py', '--help']) # Finally, you can make your step accept any return code api.step('anything is cool', ['bash', '-c', 'exit 3'], ok_ret='any') # We can manipulate the step presentation arbitrarily until we run # the next step. step_result = api.step('hello', ['echo', 'hello']) step_result.presentation.status = api.step.EXCEPTION step_result.presentation.logs['the reason'] = ['The reason\nit failed'] # Without a command, a step can be used to present some data from the recipe. step_result = api.step('Just print stuff', cmd=None) step_result.presentation.logs['more'] = ['More stuff'] try: api.step('goodbye', ['echo', 'goodbye']) # Modifying step_result now would raise an AssertionError. except api.step.StepFailure: # Raising anything besides StepFailure or StepWarning causes the build to go # purple. raise ValueError('goodbye must exit 0!') try: api.step('warning', ['echo', 'warning']) except api.step.StepFailure as e: e.result.presentation.status = api.step.WARNING raise api.step.StepWarning(e.message) # Aggregate failures from tests! try: with recipe_api.defer_results(): api.step('testa', ['echo', 'testa']) api.step('testb', ['echo', 'testb'], infra_step=True) except recipe_api.AggregatedStepFailure as f: # You can raise aggregated step failures. raise f # Some steps are needed from an infrastructure point of view. If these # steps fail, the build stops, but doesn't get turned red because it's # not the developers' fault. try: api.step('cleanup', ['echo', 'cleaning', 'up', 'build'], infra_step=True) except api.step.InfraFailure as f: assert f.result.presentation.status == api.step.EXCEPTION # Run a step through a made-up wrapper program. api.step('application', ['echo', 'main', 'application'], wrapper=['python', '-c', 'import sys; print sys.argv']) if access_invalid_data: result = api.step('no-op', ['echo', 'I', 'do', 'nothing']) # Trying to access non-existent attributes on the result should raise. _ = result.json.output if access_deep_invalid_data: result = api.step('no-op', ['echo', api.json.output()]) # Trying to access deep, non-existent attributes on the result should raise. _ = result.json.outpurt if assign_extra_junk: result = api.step('no-op', ['echo', 'I', 'do', 'nothing']) # Assigning extra junk to the result raises ValueError. result.json = "hi"
def RunSteps(api): # TODO(martinis) change this # The api.step object is directly callable. api.step("hello", ["echo", "Hello World"]) api.step("hello", ["echo", "Why hello, there."]) # You can also manipulate various aspects of the step, such as env. # These are passed straight through to subprocess.Popen. # Also, abusing bash -c in this way is a TERRIBLE IDEA DON'T DO IT. api.step("goodbye", ["bash", "-c", "echo Good bye, $friend."], env={"friend": "Darth Vader"}) # Finally, you can make your step accept any return code api.step("anything is cool", ["bash", "-c", "exit 3"], ok_ret="any") # We can manipulate the step presentation arbitrarily until we run # the next step. step_result = api.step("hello", ["echo", "hello"]) step_result.presentation.status = api.step.EXCEPTION step_result.presentation.logs["the reason"] = ["The reason\nit failed"] # Without a command, a step can be used to present some data from the recipe. step_result = api.step("Just print stuff", cmd=None) step_result.presentation.logs["more"] = ["More stuff"] try: api.step("goodbye", ["echo", "goodbye"]) # Modifying step_result now would raise an AssertionError. except api.step.StepFailure: # Raising anything besides StepFailure or StepWarning causes the build to go # purple. raise ValueError("goodbye must exit 0!") try: api.step("warning", ["echo", "warning"]) except api.step.StepFailure as e: e.result.presentation.status = api.step.WARNING raise api.step.StepWarning(e.message) # Aggregate failures from tests! try: with recipe_api.defer_results(): api.step("testa", ["echo", "testa"]) api.step("testb", ["echo", "testb"]) except recipe_api.AggregatedStepFailure as f: raise api.step.StepFailure("You can catch step failures.") # Some steps are needed from an infrastructure point of view. If these # steps fail, the build stops, but doesn't get turned red because it's # not the developers' fault. try: api.step("cleanup", ["echo", "cleaning", "up", "build"], infra_step=True) except api.step.InfraFailure as f: assert f.result.presentation.status == api.step.EXCEPTION # Run a step through a made-up wrapper program. api.step("application", ["echo", "main", "application"], wrapper=["python", "-c", "import sys; print sys.argv"]) if api.properties.get("access_invalid_data"): result = api.step("no-op", ["echo", "I", "do", "nothing"]) # Trying to access non-existent attributes on the result should raise. _ = result.json.output
def RunSteps(api, properties, env_properties): """Steps to checkout flutter engine and execute web test shard. The test shard to run will be determined by `command_args` send as part of properties. """ cache_root = api.path['cleanup'].join('builder') checkout = GetCheckoutPath(api) platform = api.platform.name.capitalize() if properties.clobber: api.file.rmtree('Clobber cache', cache_root) api.file.rmtree('Clobber build output: %s' % platform, checkout.join('out')) api.file.ensure_directory('Ensure checkout cache', cache_root) api.goma.ensure() env = {} env_prefixes = {} # Checkout source code and build api.repo_util.engine_checkout(cache_root, env, env_prefixes) # Prepare the dependencies that web tests need. # These can be browsers, web drivers or other repositories. api.web_util.prepare_dependencies(checkout) with api.context(cwd=cache_root, env=env, env_prefixes=env_prefixes), api.depot_tools.on_path(): target_name = 'host_debug_unopt' # Load local engine information if available. api.flutter_deps.flutter_engine(env, env_prefixes) android_home = checkout.join('third_party', 'android_tools', 'sdk') env['GOMA_DIR'] = api.goma.goma_dir env['ANDROID_HOME'] = str(android_home) env['CHROME_NO_SANDBOX'] = 'true' env['ENGINE_PATH'] = cache_root # flutter_engine deps adds dart dependency as out/host_debug_unopt/dart-sdk # We are changing it with src/third_party/dart/tools/sdks/dart-sdk dart_bin = checkout.join('third_party', 'dart', 'tools', 'sdks', 'dart-sdk', 'bin') paths = env_prefixes.get('PATH', []) paths.insert(0, dart_bin) env_prefixes['PATH'] = paths command_args = api.properties.get('command_args', ['test']) command_name = api.properties.get('command_name', 'test') felt_cmd = [ checkout.join('out', target_name, 'dart-sdk', 'bin', 'dart'), 'dev/felt.dart' ] felt_cmd.extend(command_args) with api.context(cwd=cache_root, env=env, env_prefixes=env_prefixes), api.depot_tools.on_path(): # Update dart packages and run tests. local_engine_path = env.get('LOCAL_ENGINE') local_pub = local_engine_path.join('dart-sdk', 'bin', 'pub') with api.context(cwd=checkout.join('flutter', 'web_sdk', 'web_engine_tester')): api.step('pub get in web_engine_tester', [local_pub, 'get']) with api.context(cwd=checkout.join('flutter', 'lib', 'web_ui')): api.step('pub get in web_ui', [local_pub, 'get']) if api.platform.is_mac: with api.osx_sdk('ios'): with recipe_api.defer_results(): api.step('felt test: %s' % command_name, felt_cmd) if api.properties.get( 'dependencies' ) and 'goldens_repo' in api.properties.get( 'dependencies'): api.web_util.upload_failing_goldens( checkout, 'ios-safari') # This is to clean up leaked processes. api.os_utils.kill_processes() # Collect memory/cpu/process after task execution. api.os_utils.collect_os_info() else: with recipe_api.defer_results(): api.step('felt test: %s' % command_name, felt_cmd) if api.properties.get( 'dependencies' ) and 'goldens_repo' in api.properties.get( 'dependencies'): api.web_util.upload_failing_goldens( checkout, 'chrome') # This is to clean up leaked processes. api.os_utils.kill_processes() # Collect memory/cpu/process after task execution. api.os_utils.collect_os_info()