diff --git a/recipes/recipe_modules/bot_update/api.py b/recipes/recipe_modules/bot_update/api.py index a52ec88e3..7e1e1d6db 100644 --- a/recipes/recipe_modules/bot_update/api.py +++ b/recipes/recipe_modules/bot_update/api.py @@ -269,6 +269,7 @@ class BotUpdateApi(recipe_api.RecipeApi): raise finally: if step_result: + result = step_result.json.output self._last_returned_properties = step_result.json.output.get( 'properties', {}) @@ -278,44 +279,38 @@ class BotUpdateApi(recipe_api.RecipeApi): self.last_returned_properties.iteritems()): step_result.presentation.properties[prop_name] = prop_value # Add helpful step description in the step UI. - if 'step_text' in step_result.json.output: - step_text = step_result.json.output['step_text'] + if 'step_text' in result: + step_text = result['step_text'] step_result.presentation.step_text = step_text - # Add log line output. - if 'log_lines' in step_result.json.output: - for log_name, log_lines in step_result.json.output['log_lines']: - step_result.presentation.logs[log_name] = log_lines.splitlines() # Set the "checkout" path for the main solution. # This is used by the Chromium module to figure out where to look for # the checkout. # If there is a patch failure, emit another step that said things # failed. - if step_result.json.output.get('patch_failure'): - return_code = step_result.json.output.get('patch_apply_return_code') - if return_code == 3: - # This is download failure, hence an infra failure. - # Sadly, python.failing_step doesn't support kwargs. - self.m.python.inline( - 'Patch failure - Try Rebasing', - ('import sys;' - 'print "Patch download failed. See bot_update step for' - ' details";sys.exit(1)'), - infra_step=True, - step_test_data=lambda: self.m.raw_io.test_api.output( - 'Patch download failed. See bot_update step for details', - retcode=1) - ) - else: - # This is actual patch failure. - self.m.tryserver.set_patch_failure_tryjob_result() - self.m.python.failing_step( - 'Patch failure', 'Check the bot_update step for details') + if result.get('patch_failure'): + return_code = result.get('patch_apply_return_code') + patch_body = result.get('failed_patch_body') + try: + if return_code == 3: + # This is download failure, hence an infra failure. + with self.m.step.context({'infra_step': True}): + self.m.python.failing_step( + 'Patch failure', 'Git reported a download failure') + else: + # This is actual patch failure. + self.m.tryserver.set_patch_failure_tryjob_result() + self.m.python.failing_step( + 'Patch failure', 'See attached log. Try rebasing?') + except self.m.step.StepFailure as e: + if patch_body: + e.result.presentation.logs['patch error'] = ( + patch_body.splitlines()) # bot_update actually just sets root to be the folder name of the # first solution. - if step_result.json.output['did_run']: - co_root = step_result.json.output['root'] + if result['did_run']: + co_root = result['root'] cwd = self.m.step.get_from_context('cwd', self.m.path['start_dir']) if 'checkout' not in self.m.path: self.m.path['checkout'] = cwd.join(*co_root.split(self.m.path.sep)) diff --git a/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json b/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json index 026ae099d..0ce9242d5 100644 --- a/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json +++ b/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json @@ -34,15 +34,10 @@ "@@@STEP_TEXT@Some step text@@@", "@@@STEP_LOG_LINE@json.output@{@@@", "@@@STEP_LOG_LINE@json.output@ \"did_run\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"failed_patch_body\": \"Downloading patch...\\nApplying the patch...\\nPatch: foo/bar.py\\nIndex: foo/bar.py\\ndiff --git a/foo/bar.py b/foo/bar.py\\nindex HASH..HASH MODE\\n--- a/foo/bar.py\\n+++ b/foo/bar.py\\ncontext\\n+something\\n-something\\nmore context\", @@@", "@@@STEP_LOG_LINE@json.output@ \"fixed_revisions\": {@@@", "@@@STEP_LOG_LINE@json.output@ \"src\": \"HEAD\"@@@", "@@@STEP_LOG_LINE@json.output@ }, @@@", - "@@@STEP_LOG_LINE@json.output@ \"log_lines\": [@@@", - "@@@STEP_LOG_LINE@json.output@ [@@@", - "@@@STEP_LOG_LINE@json.output@ \"patch error\", @@@", - "@@@STEP_LOG_LINE@json.output@ \"Patch failed to apply\"@@@", - "@@@STEP_LOG_LINE@json.output@ ]@@@", - "@@@STEP_LOG_LINE@json.output@ ], @@@", "@@@STEP_LOG_LINE@json.output@ \"patch_apply_return_code\": 1, @@@", "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": true, @@@", "@@@STEP_LOG_LINE@json.output@ \"patch_root\": \"src\", @@@", @@ -54,8 +49,6 @@ "@@@STEP_LOG_LINE@json.output@ \"step_text\": \"Some step text\"@@@", "@@@STEP_LOG_LINE@json.output@}@@@", "@@@STEP_LOG_END@json.output@@@", - "@@@STEP_LOG_LINE@patch error@Patch failed to apply@@@", - "@@@STEP_LOG_END@patch error@@@", "@@@SET_BUILD_PROPERTY@got_cr_revision@\"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", "@@@SET_BUILD_PROPERTY@got_cr_revision_cp@\"refs/heads/master@{#170242}\"@@@", "@@@SET_BUILD_PROPERTY@failure_type@\"PATCH_FAILURE\"@@@" @@ -70,14 +63,108 @@ "name": "Patch failure", "~followup_annotations": [ "step returned non-zero exit code: 1", - "@@@STEP_TEXT@Check the bot_update step for details@@@", + "@@@STEP_TEXT@See attached log. Try rebasing?@@@", + "@@@STEP_LOG_LINE@patch error@Downloading patch...@@@", + "@@@STEP_LOG_LINE@patch error@Applying the patch...@@@", + "@@@STEP_LOG_LINE@patch error@Patch: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@Index: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@diff --git a/foo/bar.py b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@index HASH..HASH MODE@@@", + "@@@STEP_LOG_LINE@patch error@--- a/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@+++ b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@context@@@", + "@@@STEP_LOG_LINE@patch error@+something@@@", + "@@@STEP_LOG_LINE@patch error@-something@@@", + "@@@STEP_LOG_LINE@patch error@more context@@@", + "@@@STEP_LOG_END@patch error@@@", + "@@@STEP_FAILURE@@@" + ] + }, + { + "cmd": [ + "python", + "-u", + "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", + "--spec", + "cache_dir = '[GIT_CACHE]'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': True, 'name': 'src', 'url': 'https://chromium.googlesource.com/chromium/src.git'}]", + "--patch_root", + "src", + "--revision_mapping_file", + "{\"src\": \"got_cr_revision\"}", + "--git-cache-dir", + "[GIT_CACHE]", + "--rietveld_server", + "https://rietveld.example.com/", + "--output_json", + "/path/to/tmp/json", + "--revision", + "src@f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9", + "--output_manifest" + ], + "env": { + "GIT_HTTP_LOW_SPEED_LIMIT": "1000", + "GIT_HTTP_LOW_SPEED_TIME": "300", + "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]" + }, + "infra_step": true, + "name": "bot_update (without patch)", + "~followup_annotations": [ + "@@@STEP_TEXT@Some step text@@@", + "@@@STEP_LOG_LINE@json.output@{@@@", + "@@@STEP_LOG_LINE@json.output@ \"did_run\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"failed_patch_body\": \"Downloading patch...\\nApplying the patch...\\nPatch: foo/bar.py\\nIndex: foo/bar.py\\ndiff --git a/foo/bar.py b/foo/bar.py\\nindex HASH..HASH MODE\\n--- a/foo/bar.py\\n+++ b/foo/bar.py\\ncontext\\n+something\\n-something\\nmore context\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"fixed_revisions\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"src\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"manifest\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"src\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"repository\": \"https://fake.org/src.git\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"revision\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", + "@@@STEP_LOG_LINE@json.output@ }@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_apply_return_code\": 1, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_root\": \"src\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"properties\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"got_cr_revision\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"got_cr_revision_cp\": \"refs/heads/master@{#170242}\"@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"root\": \"src\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"step_text\": \"Some step text\"@@@", + "@@@STEP_LOG_LINE@json.output@}@@@", + "@@@STEP_LOG_END@json.output@@@", + "@@@SET_BUILD_PROPERTY@failure_type@\"PATCH_FAILURE\"@@@" + ] + }, + { + "cmd": [ + "python", + "-u", + "import sys; sys.exit(1)" + ], + "name": "Patch failure (2)", + "~followup_annotations": [ + "step returned non-zero exit code: 1", + "@@@STEP_TEXT@See attached log. Try rebasing?@@@", + "@@@STEP_LOG_LINE@patch error@Downloading patch...@@@", + "@@@STEP_LOG_LINE@patch error@Applying the patch...@@@", + "@@@STEP_LOG_LINE@patch error@Patch: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@Index: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@diff --git a/foo/bar.py b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@index HASH..HASH MODE@@@", + "@@@STEP_LOG_LINE@patch error@--- a/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@+++ b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@context@@@", + "@@@STEP_LOG_LINE@patch error@+something@@@", + "@@@STEP_LOG_LINE@patch error@-something@@@", + "@@@STEP_LOG_LINE@patch error@more context@@@", + "@@@STEP_LOG_END@patch error@@@", "@@@STEP_FAILURE@@@" ] }, { "name": "$result", - "reason": "Step('Patch failure') failed with return_code 1", "recipe_result": null, - "status_code": 1 + "status_code": 0 } ] \ No newline at end of file diff --git a/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json b/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json index b8e39678f..6e7dcfc4e 100644 --- a/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json +++ b/recipes/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json @@ -34,15 +34,10 @@ "@@@STEP_TEXT@Some step text@@@", "@@@STEP_LOG_LINE@json.output@{@@@", "@@@STEP_LOG_LINE@json.output@ \"did_run\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"failed_patch_body\": \"Downloading patch...\\nApplying the patch...\\nPatch: foo/bar.py\\nIndex: foo/bar.py\\ndiff --git a/foo/bar.py b/foo/bar.py\\nindex HASH..HASH MODE\\n--- a/foo/bar.py\\n+++ b/foo/bar.py\\ncontext\\n+something\\n-something\\nmore context\", @@@", "@@@STEP_LOG_LINE@json.output@ \"fixed_revisions\": {@@@", "@@@STEP_LOG_LINE@json.output@ \"src\": \"HEAD\"@@@", "@@@STEP_LOG_LINE@json.output@ }, @@@", - "@@@STEP_LOG_LINE@json.output@ \"log_lines\": [@@@", - "@@@STEP_LOG_LINE@json.output@ [@@@", - "@@@STEP_LOG_LINE@json.output@ \"patch error\", @@@", - "@@@STEP_LOG_LINE@json.output@ \"Patch failed to apply\"@@@", - "@@@STEP_LOG_LINE@json.output@ ]@@@", - "@@@STEP_LOG_LINE@json.output@ ], @@@", "@@@STEP_LOG_LINE@json.output@ \"patch_apply_return_code\": 3, @@@", "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": true, @@@", "@@@STEP_LOG_LINE@json.output@ \"patch_root\": \"src\", @@@", @@ -54,8 +49,6 @@ "@@@STEP_LOG_LINE@json.output@ \"step_text\": \"Some step text\"@@@", "@@@STEP_LOG_LINE@json.output@}@@@", "@@@STEP_LOG_END@json.output@@@", - "@@@STEP_LOG_LINE@patch error@Patch failed to apply@@@", - "@@@STEP_LOG_END@patch error@@@", "@@@SET_BUILD_PROPERTY@got_cr_revision@\"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", "@@@SET_BUILD_PROPERTY@got_cr_revision_cp@\"refs/heads/master@{#170242}\"@@@" ] @@ -64,21 +57,114 @@ "cmd": [ "python", "-u", - "import sys;print \"Patch download failed. See bot_update step for details\";sys.exit(1)" + "import sys; sys.exit(1)" + ], + "infra_step": true, + "name": "Patch failure", + "~followup_annotations": [ + "step returned non-zero exit code: 1", + "@@@STEP_TEXT@Git reported a download failure@@@", + "@@@STEP_LOG_LINE@patch error@Downloading patch...@@@", + "@@@STEP_LOG_LINE@patch error@Applying the patch...@@@", + "@@@STEP_LOG_LINE@patch error@Patch: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@Index: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@diff --git a/foo/bar.py b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@index HASH..HASH MODE@@@", + "@@@STEP_LOG_LINE@patch error@--- a/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@+++ b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@context@@@", + "@@@STEP_LOG_LINE@patch error@+something@@@", + "@@@STEP_LOG_LINE@patch error@-something@@@", + "@@@STEP_LOG_LINE@patch error@more context@@@", + "@@@STEP_LOG_END@patch error@@@", + "@@@STEP_EXCEPTION@@@" + ] + }, + { + "cmd": [ + "python", + "-u", + "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", + "--spec", + "cache_dir = '[GIT_CACHE]'\nsolutions = [{'deps_file': '.DEPS.git', 'managed': True, 'name': 'src', 'url': 'https://chromium.googlesource.com/chromium/src.git'}]", + "--patch_root", + "src", + "--revision_mapping_file", + "{\"src\": \"got_cr_revision\"}", + "--git-cache-dir", + "[GIT_CACHE]", + "--rietveld_server", + "https://rietveld.example.com/", + "--output_json", + "/path/to/tmp/json", + "--revision", + "src@f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9", + "--output_manifest" + ], + "env": { + "GIT_HTTP_LOW_SPEED_LIMIT": "1000", + "GIT_HTTP_LOW_SPEED_TIME": "300", + "PATH": "%(PATH)s:RECIPE_PACKAGE_REPO[depot_tools]" + }, + "infra_step": true, + "name": "bot_update (without patch)", + "~followup_annotations": [ + "@@@STEP_TEXT@Some step text@@@", + "@@@STEP_LOG_LINE@json.output@{@@@", + "@@@STEP_LOG_LINE@json.output@ \"did_run\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"failed_patch_body\": \"Downloading patch...\\nApplying the patch...\\nPatch: foo/bar.py\\nIndex: foo/bar.py\\ndiff --git a/foo/bar.py b/foo/bar.py\\nindex HASH..HASH MODE\\n--- a/foo/bar.py\\n+++ b/foo/bar.py\\ncontext\\n+something\\n-something\\nmore context\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"fixed_revisions\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"src\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"manifest\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"src\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"repository\": \"https://fake.org/src.git\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"revision\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", + "@@@STEP_LOG_LINE@json.output@ }@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_apply_return_code\": 3, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_root\": \"src\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"properties\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"got_cr_revision\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"got_cr_revision_cp\": \"refs/heads/master@{#170242}\"@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"root\": \"src\", @@@", + "@@@STEP_LOG_LINE@json.output@ \"step_text\": \"Some step text\"@@@", + "@@@STEP_LOG_LINE@json.output@}@@@", + "@@@STEP_LOG_END@json.output@@@" + ] + }, + { + "cmd": [ + "python", + "-u", + "import sys; sys.exit(1)" ], "infra_step": true, - "name": "Patch failure - Try Rebasing", + "name": "Patch failure (2)", "~followup_annotations": [ "step returned non-zero exit code: 1", - "@@@STEP_LOG_LINE@python.inline@import sys;print \"Patch download failed. See bot_update step for details\";sys.exit(1)@@@", - "@@@STEP_LOG_END@python.inline@@@", + "@@@STEP_TEXT@Git reported a download failure@@@", + "@@@STEP_LOG_LINE@patch error@Downloading patch...@@@", + "@@@STEP_LOG_LINE@patch error@Applying the patch...@@@", + "@@@STEP_LOG_LINE@patch error@Patch: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@Index: foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@diff --git a/foo/bar.py b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@index HASH..HASH MODE@@@", + "@@@STEP_LOG_LINE@patch error@--- a/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@+++ b/foo/bar.py@@@", + "@@@STEP_LOG_LINE@patch error@context@@@", + "@@@STEP_LOG_LINE@patch error@+something@@@", + "@@@STEP_LOG_LINE@patch error@-something@@@", + "@@@STEP_LOG_LINE@patch error@more context@@@", + "@@@STEP_LOG_END@patch error@@@", "@@@STEP_EXCEPTION@@@" ] }, { "name": "$result", - "reason": "Infra Failure: Step('Patch failure - Try Rebasing') returned 1", "recipe_result": null, - "status_code": 1 + "status_code": 0 } ] \ No newline at end of file diff --git a/recipes/recipe_modules/bot_update/resources/bot_update.py b/recipes/recipe_modules/bot_update/resources/bot_update.py index 07987b089..8d24ecc32 100755 --- a/recipes/recipe_modules/bot_update/resources/bot_update.py +++ b/recipes/recipe_modules/bot_update/resources/bot_update.py @@ -424,17 +424,6 @@ def get_commit_message_footer(message, key): return get_commit_message_footer_map(message).get(key) -def emit_log_lines(name, lines): - for line in lines.splitlines(): - print '@@@STEP_LOG_LINE@%s@%s@@@' % (name, line) - print '@@@STEP_LOG_END@%s@@@' % name - - -def emit_properties(properties): - for property_name, property_value in sorted(properties.items()): - print '@@@SET_BUILD_PROPERTY@%s@"%s"@@@' % (property_name, property_value) - - # Derived from: # http://code.activestate.com/recipes/577972-disk-usage/?in=user-4178764 def get_total_disk_space(): @@ -1070,16 +1059,12 @@ def checkout(options, git_slns, specs, revisions, step_text, shallow): emit_json(options.output_json, did_run=True, root=first_sln, - log_lines=[('patch error', e.output),], patch_apply_return_code=e.code, patch_root=options.patch_root, patch_failure=True, + failed_patch_body=e.output, step_text='%s PATCH FAILED' % step_text, fixed_revisions=revisions) - else: - # If we're not on recipes, tell annotator about our got_revisions. - emit_log_lines('patch error', e.output) - print '@@@STEP_TEXT@%s PATCH FAILED@@@' % step_text raise # Take care of got_revisions outputs. @@ -1113,9 +1098,6 @@ def checkout(options, git_slns, specs, revisions, step_text, shallow): fixed_revisions=revisions, properties=got_revisions, manifest=manifest) - else: - # If we're not on recipes, tell annotator about our got_revisions. - emit_properties(got_revisions) def print_debug_info(): diff --git a/recipes/recipe_modules/bot_update/test_api.py b/recipes/recipe_modules/bot_update/test_api.py index 7294052b1..0c19ca1b6 100644 --- a/recipes/recipe_modules/bot_update/test_api.py +++ b/recipes/recipe_modules/bot_update/test_api.py @@ -50,8 +50,21 @@ class BotUpdateTestApi(recipe_test_api.RecipeTestApi): output['fixed_revisions'] = fixed_revisions if fail_patch: - output['log_lines'] = [('patch error', 'Patch failed to apply'),] output['patch_failure'] = True + output['failed_patch_body'] = '\n'.join([ + 'Downloading patch...', + 'Applying the patch...', + 'Patch: foo/bar.py', + 'Index: foo/bar.py', + 'diff --git a/foo/bar.py b/foo/bar.py', + 'index HASH..HASH MODE', + '--- a/foo/bar.py', + '+++ b/foo/bar.py', + 'context', + '+something', + '-something', + 'more context', + ]) output['patch_apply_return_code'] = 1 if fail_patch == 'download': output['patch_apply_return_code'] = 3