From b0f36188b2840a84f9da45a9f1e0444d732d72c1 Mon Sep 17 00:00:00 2001 From: "hinoka@chromium.org" Date: Tue, 16 Feb 2016 18:11:08 +0000 Subject: [PATCH] Bot update cleanup * Remove activation check * Remove messages * Remove deps2git * Remove build_internal pointer by: ** Moving chrome svn url into bot_update.py (This isn't secret anyways.) ** Move patch.exe into depot_tools (No reason this should've been internal...) ** Default everything to active, so no need for checks anyways. BUG= Review URL: https://codereview.chromium.org/1686273002 git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@298809 0039d316-1c4b-4281-b951-d872f2087c98 --- recipe_modules/bot_update/api.py | 31 +- .../bot_update/example.expected/basic.json | 6 - .../basic_output_manifest.json | 6 - .../basic_with_branch_heads.json | 6 - .../bot_update/example.expected/clobber.json | 25 +- .../bot_update/example.expected/forced.json | 9 +- .../example.expected/no_shallow.json | 25 +- .../bot_update/example.expected/off.json | 44 -- .../reset_root_solution_revision.json | 25 +- .../bot_update/example.expected/svn_mode.json | 60 --- .../example.expected/trychange.json | 6 - .../example.expected/trychange_oauth2.json | 6 - .../bot_update/example.expected/tryjob.json | 6 - .../example.expected/tryjob_fail.json | 6 - .../example.expected/tryjob_fail_patch.json | 6 - .../tryjob_fail_patch_download.json | 6 - .../example.expected/tryjob_v8.json | 6 - recipe_modules/bot_update/example.py | 53 --- .../bot_update/resources/bot_update.py | 401 ++---------------- recipe_modules/bot_update/resources/patch.exe | Bin 0 -> 112640 bytes recipe_modules/bot_update/test_api.py | 82 ++-- tests/bot_update_coverage_test.py | 261 ++++++++++++ 22 files changed, 380 insertions(+), 696 deletions(-) delete mode 100644 recipe_modules/bot_update/example.expected/off.json delete mode 100644 recipe_modules/bot_update/example.expected/svn_mode.json create mode 100755 recipe_modules/bot_update/resources/patch.exe create mode 100755 tests/bot_update_coverage_test.py diff --git a/recipe_modules/bot_update/api.py b/recipe_modules/bot_update/api.py index 4b870a4b7..610872e5f 100644 --- a/recipe_modules/bot_update/api.py +++ b/recipe_modules/bot_update/api.py @@ -8,12 +8,6 @@ from recipe_engine import recipe_api -# This is just for testing, to indicate if a master is using a Git scheduler -# or not. -SVN_MASTERS = ( - 'experimental.svn', -) - def jsonish_to_python(spec, is_top=False): """Turn a json spec into a python parsable object. @@ -67,9 +61,10 @@ class BotUpdateApi(recipe_api.RecipeApi): def properties(self): return self._properties + # Note: force is ignored. def ensure_checkout(self, gclient_config=None, suffix=None, patch=True, update_presentation=True, - force=False, patch_root=None, no_shallow=False, + force=True, patch_root=None, no_shallow=False, with_branch_heads=False, refs=None, patch_project_roots=None, patch_oauth2=False, output_manifest=True, clobber=False, @@ -80,11 +75,6 @@ class BotUpdateApi(recipe_api.RecipeApi): cfg = gclient_config or self.m.gclient.c spec_string = jsonish_to_python(cfg.as_jsonish(), True) - # Used by bot_update to determine if we want to run or not. - master = self.m.properties['mastername'] - builder = self.m.properties['buildername'] - slave = self.m.properties['slavename'] - # Construct our bot_update command. This basically be inclusive of # everything required for bot_update to know: root = patch_root @@ -136,18 +126,13 @@ class BotUpdateApi(recipe_api.RecipeApi): rev_map = self.m.gclient.c.got_revision_mapping.as_jsonish() flags = [ - # 1. Do we want to run? (master/builder/slave). - ['--master', master], - ['--builder', builder], - ['--slave', slave], - - # 2. What do we want to check out (spec/root/rev/rev_map). + # 1. What do we want to check out (spec/root/rev/rev_map). ['--spec', spec_string], ['--root', root], ['--revision_mapping_file', self.m.json.input(rev_map)], ['--git-cache-dir', self.m.path['git_cache']], - # 3. How to find the patch, if any (issue/patchset/patch_url). + # 2. How to find the patch, if any (issue/patchset/patch_url). ['--issue', issue], ['--patchset', patchset], ['--patch_url', patch_url], @@ -157,7 +142,7 @@ class BotUpdateApi(recipe_api.RecipeApi): ['--apply_issue_email_file', email_file], ['--apply_issue_key_file', key_file], - # 4. Hookups to JSON output back into recipes. + # 3. Hookups to JSON output back into recipes. ['--output_json', self.m.json.output()],] @@ -197,8 +182,6 @@ class BotUpdateApi(recipe_api.RecipeApi): if clobber: cmd.append('--clobber') - if force: - cmd.append('--force') if no_shallow: cmd.append('--no_shallow') if output_manifest: @@ -207,11 +190,9 @@ class BotUpdateApi(recipe_api.RecipeApi): cmd.append('--with_branch_heads') # Inject Json output for testing. - git_mode = self.m.properties.get('mastername') not in SVN_MASTERS first_sln = cfg.solutions[0].name step_test_data = lambda: self.test_api.output_json( - master, builder, slave, root, first_sln, rev_map, git_mode, force, - self.m.properties.get('fail_patch', False), + root, first_sln, rev_map, self.m.properties.get('fail_patch', False), output_manifest=output_manifest, fixed_revisions=fixed_revisions) # Add suffixes to the step name, if specified. diff --git a/recipe_modules/bot_update/example.expected/basic.json b/recipe_modules/bot_update/example.expected/basic.json index 0cff6c0fe..0ad0c943f 100644 --- a/recipe_modules/bot_update/example.expected/basic.json +++ b/recipe_modules/bot_update/example.expected/basic.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "chromium.linux", - "--builder", - "Linux Builder", - "--slave", - "totallyaslave-m1", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/basic_output_manifest.json b/recipe_modules/bot_update/example.expected/basic_output_manifest.json index 170b4ab6d..6dbca9a27 100644 --- a/recipe_modules/bot_update/example.expected/basic_output_manifest.json +++ b/recipe_modules/bot_update/example.expected/basic_output_manifest.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "chromium.linux", - "--builder", - "Linux Builder", - "--slave", - "totallyaslave-m1", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/basic_with_branch_heads.json b/recipe_modules/bot_update/example.expected/basic_with_branch_heads.json index 214604319..67d212db9 100644 --- a/recipe_modules/bot_update/example.expected/basic_with_branch_heads.json +++ b/recipe_modules/bot_update/example.expected/basic_with_branch_heads.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "chromium.linux", - "--builder", - "Linux Builder", - "--slave", - "totallyaslave-m1", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/clobber.json b/recipe_modules/bot_update/example.expected/clobber.json index ee6f935c9..2adc26fdd 100644 --- a/recipe_modules/bot_update/example.expected/clobber.json +++ b/recipe_modules/bot_update/example.expected/clobber.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "experimental", - "--builder", - "Experimental Builder", - "--slave", - "somehost", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", @@ -30,11 +24,24 @@ }, "name": "bot_update", "~followup_annotations": [ + "@@@STEP_TEXT@Some step text@@@", "@@@STEP_LOG_LINE@json.output@{@@@", - "@@@STEP_LOG_LINE@json.output@ \"did_run\": false, @@@", - "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false@@@", + "@@@STEP_LOG_LINE@json.output@ \"did_run\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"fixed_revisions\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"src\": \"HEAD\"@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false, @@@", + "@@@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@@@" + "@@@STEP_LOG_END@json.output@@@", + "@@@SET_BUILD_PROPERTY@got_cr_revision@\"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", + "@@@SET_BUILD_PROPERTY@got_cr_revision_cp@\"refs/heads/master@{#170242}\"@@@" ] }, { diff --git a/recipe_modules/bot_update/example.expected/forced.json b/recipe_modules/bot_update/example.expected/forced.json index 07b1af682..a043d5a12 100644 --- a/recipe_modules/bot_update/example.expected/forced.json +++ b/recipe_modules/bot_update/example.expected/forced.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "experimental", - "--builder", - "Experimental Builder", - "--slave", - "somehost", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", @@ -21,8 +15,7 @@ "--output_json", "/path/to/tmp/json", "--revision", - "src@HEAD", - "--force" + "src@HEAD" ], "cwd": "[SLAVE_BUILD]", "env": { diff --git a/recipe_modules/bot_update/example.expected/no_shallow.json b/recipe_modules/bot_update/example.expected/no_shallow.json index 2008a4fa3..18abe3181 100644 --- a/recipe_modules/bot_update/example.expected/no_shallow.json +++ b/recipe_modules/bot_update/example.expected/no_shallow.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "experimental", - "--builder", - "Experimental Builder", - "--slave", - "somehost", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", @@ -30,11 +24,24 @@ }, "name": "bot_update", "~followup_annotations": [ + "@@@STEP_TEXT@Some step text@@@", "@@@STEP_LOG_LINE@json.output@{@@@", - "@@@STEP_LOG_LINE@json.output@ \"did_run\": false, @@@", - "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false@@@", + "@@@STEP_LOG_LINE@json.output@ \"did_run\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"fixed_revisions\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"src\": \"HEAD\"@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false, @@@", + "@@@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@@@" + "@@@STEP_LOG_END@json.output@@@", + "@@@SET_BUILD_PROPERTY@got_cr_revision@\"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", + "@@@SET_BUILD_PROPERTY@got_cr_revision_cp@\"refs/heads/master@{#170242}\"@@@" ] }, { diff --git a/recipe_modules/bot_update/example.expected/off.json b/recipe_modules/bot_update/example.expected/off.json deleted file mode 100644 index 48109b0ba..000000000 --- a/recipe_modules/bot_update/example.expected/off.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "cmd": [ - "python", - "-u", - "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "experimental", - "--builder", - "Experimental Builder", - "--slave", - "somehost", - "--spec", - "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", - "--root", - "src", - "--revision_mapping_file", - "{\"src\": \"got_cr_revision\"}", - "--git-cache-dir", - "[GIT_CACHE]", - "--output_json", - "/path/to/tmp/json", - "--revision", - "src@HEAD" - ], - "cwd": "[SLAVE_BUILD]", - "env": { - "PATH": "%(PATH)s:RECIPE_PACKAGE[depot_tools]" - }, - "name": "bot_update", - "~followup_annotations": [ - "@@@STEP_LOG_LINE@json.output@{@@@", - "@@@STEP_LOG_LINE@json.output@ \"did_run\": false, @@@", - "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false@@@", - "@@@STEP_LOG_LINE@json.output@}@@@", - "@@@STEP_LOG_END@json.output@@@" - ] - }, - { - "name": "$result", - "recipe_result": null, - "status_code": 0 - } -] \ No newline at end of file diff --git a/recipe_modules/bot_update/example.expected/reset_root_solution_revision.json b/recipe_modules/bot_update/example.expected/reset_root_solution_revision.json index e50485c41..69370788f 100644 --- a/recipe_modules/bot_update/example.expected/reset_root_solution_revision.json +++ b/recipe_modules/bot_update/example.expected/reset_root_solution_revision.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "experimental", - "--builder", - "Experimental Builder", - "--slave", - "somehost", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", @@ -29,11 +23,24 @@ }, "name": "bot_update", "~followup_annotations": [ + "@@@STEP_TEXT@Some step text@@@", "@@@STEP_LOG_LINE@json.output@{@@@", - "@@@STEP_LOG_LINE@json.output@ \"did_run\": false, @@@", - "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false@@@", + "@@@STEP_LOG_LINE@json.output@ \"did_run\": true, @@@", + "@@@STEP_LOG_LINE@json.output@ \"fixed_revisions\": {@@@", + "@@@STEP_LOG_LINE@json.output@ \"src\": \"revision\"@@@", + "@@@STEP_LOG_LINE@json.output@ }, @@@", + "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false, @@@", + "@@@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@@@" + "@@@STEP_LOG_END@json.output@@@", + "@@@SET_BUILD_PROPERTY@got_cr_revision@\"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", + "@@@SET_BUILD_PROPERTY@got_cr_revision_cp@\"refs/heads/master@{#170242}\"@@@" ] }, { diff --git a/recipe_modules/bot_update/example.expected/svn_mode.json b/recipe_modules/bot_update/example.expected/svn_mode.json deleted file mode 100644 index b12f9e40c..000000000 --- a/recipe_modules/bot_update/example.expected/svn_mode.json +++ /dev/null @@ -1,60 +0,0 @@ -[ - { - "cmd": [ - "python", - "-u", - "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "experimental.svn", - "--builder", - "Experimental SVN Builder", - "--slave", - "somehost", - "--spec", - "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", - "--root", - "src", - "--revision_mapping_file", - "{\"src\": \"got_cr_revision\"}", - "--git-cache-dir", - "[GIT_CACHE]", - "--output_json", - "/path/to/tmp/json", - "--revision", - "src@HEAD", - "--force" - ], - "cwd": "[SLAVE_BUILD]", - "env": { - "PATH": "%(PATH)s:RECIPE_PACKAGE[depot_tools]" - }, - "name": "bot_update", - "~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@ \"fixed_revisions\": {@@@", - "@@@STEP_LOG_LINE@json.output@ \"src\": \"HEAD\"@@@", - "@@@STEP_LOG_LINE@json.output@ }, @@@", - "@@@STEP_LOG_LINE@json.output@ \"patch_failure\": false, @@@", - "@@@STEP_LOG_LINE@json.output@ \"patch_root\": \"src\", @@@", - "@@@STEP_LOG_LINE@json.output@ \"properties\": {@@@", - "@@@STEP_LOG_LINE@json.output@ \"got_cr_revision\": 170242, @@@", - "@@@STEP_LOG_LINE@json.output@ \"got_cr_revision_cp\": \"refs/heads/master@{#170242}\", @@@", - "@@@STEP_LOG_LINE@json.output@ \"got_cr_revision_git\": \"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", - "@@@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@got_cr_revision@170242@@@", - "@@@SET_BUILD_PROPERTY@got_cr_revision_git@\"f27fede2220bcd326aee3e86ddfd4ebd0fe58cb9\"@@@", - "@@@SET_BUILD_PROPERTY@got_cr_revision_cp@\"refs/heads/master@{#170242}\"@@@" - ] - }, - { - "name": "$result", - "recipe_result": null, - "status_code": 0 - } -] \ No newline at end of file diff --git a/recipe_modules/bot_update/example.expected/trychange.json b/recipe_modules/bot_update/example.expected/trychange.json index a7aa47328..a4c0f646c 100644 --- a/recipe_modules/bot_update/example.expected/trychange.json +++ b/recipe_modules/bot_update/example.expected/trychange.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "tryserver.chromium.linux", - "--builder", - "linux_rel", - "--slave", - "totallyaslave-c4", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/trychange_oauth2.json b/recipe_modules/bot_update/example.expected/trychange_oauth2.json index c81ede5ff..f364c3bc8 100644 --- a/recipe_modules/bot_update/example.expected/trychange_oauth2.json +++ b/recipe_modules/bot_update/example.expected/trychange_oauth2.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "tryserver.chromium.linux", - "--builder", - "linux_rel", - "--slave", - "totallyaslave-c4", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/tryjob.json b/recipe_modules/bot_update/example.expected/tryjob.json index f34e0fe7e..7d2f32a58 100644 --- a/recipe_modules/bot_update/example.expected/tryjob.json +++ b/recipe_modules/bot_update/example.expected/tryjob.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "tryserver.chromium.linux", - "--builder", - "linux_rel", - "--slave", - "totallyaslave-c4", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/tryjob_fail.json b/recipe_modules/bot_update/example.expected/tryjob_fail.json index 66b377c29..b8979be0b 100644 --- a/recipe_modules/bot_update/example.expected/tryjob_fail.json +++ b/recipe_modules/bot_update/example.expected/tryjob_fail.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "tryserver.chromium.linux", - "--builder", - "linux_rel", - "--slave", - "totallyaslave-c4", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json b/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json index 9e2cd6f8d..b1163786b 100644 --- a/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json +++ b/recipe_modules/bot_update/example.expected/tryjob_fail_patch.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "tryserver.chromium.linux", - "--builder", - "linux_rel", - "--slave", - "totallyaslave-c4", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json b/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json index e7a93bdf1..6c38ce865 100644 --- a/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json +++ b/recipe_modules/bot_update/example.expected/tryjob_fail_patch_download.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "tryserver.chromium.linux", - "--builder", - "linux_rel", - "--slave", - "totallyaslave-c4", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.expected/tryjob_v8.json b/recipe_modules/bot_update/example.expected/tryjob_v8.json index f426d8b95..6d4d75afb 100644 --- a/recipe_modules/bot_update/example.expected/tryjob_v8.json +++ b/recipe_modules/bot_update/example.expected/tryjob_v8.json @@ -4,12 +4,6 @@ "python", "-u", "RECIPE_MODULE[depot_tools::bot_update]/resources/bot_update.py", - "--master", - "tryserver.chromium.linux", - "--builder", - "linux_rel", - "--slave", - "totallyaslave-c4", "--spec", "cache_dir = None\nsolutions = [{'deps_file': 'DEPS', 'managed': True, 'name': 'src', 'url': 'svn://svn.chromium.org/chrome/trunk/src'}]", "--root", diff --git a/recipe_modules/bot_update/example.py b/recipe_modules/bot_update/example.py index d866c0ceb..3e512e774 100644 --- a/recipe_modules/bot_update/example.py +++ b/recipe_modules/bot_update/example.py @@ -43,110 +43,57 @@ def RunSteps(api): def GenTests(api): yield api.test('basic') + api.properties( - mastername='chromium.linux', - buildername='Linux Builder', - slavename='totallyaslave-m1', patch=False, revision='abc' ) yield api.test('basic_with_branch_heads') + api.properties( - mastername='chromium.linux', - buildername='Linux Builder', - slavename='totallyaslave-m1', with_branch_heads=True, suffix='with branch heads' ) yield api.test('basic_output_manifest') + api.properties( - mastername='chromium.linux', - buildername='Linux Builder', - slavename='totallyaslave-m1', output_manifest=True, ) yield api.test('tryjob') + api.properties( - mastername='tryserver.chromium.linux', - buildername='linux_rel', - slavename='totallyaslave-c4', issue=12345, patchset=654321, patch_url='http://src.chromium.org/foo/bar' ) yield api.test('trychange') + api.properties( - mastername='tryserver.chromium.linux', - buildername='linux_rel', - slavename='totallyaslave-c4', refs=['+refs/change/1/2/333'], ) yield api.test('trychange_oauth2') + api.properties( - mastername='tryserver.chromium.linux', - buildername='linux_rel', - slavename='totallyaslave-c4', oauth2=True, ) yield api.test('tryjob_fail') + api.properties( - mastername='tryserver.chromium.linux', - buildername='linux_rel', - slavename='totallyaslave-c4', issue=12345, patchset=654321, patch_url='http://src.chromium.org/foo/bar', ) + api.step_data('bot_update', retcode=1) yield api.test('tryjob_fail_patch') + api.properties( - mastername='tryserver.chromium.linux', - buildername='linux_rel', - slavename='totallyaslave-c4', issue=12345, patchset=654321, patch_url='http://src.chromium.org/foo/bar', fail_patch='apply', ) + api.step_data('bot_update', retcode=88) yield api.test('tryjob_fail_patch_download') + api.properties( - mastername='tryserver.chromium.linux', - buildername='linux_rel', - slavename='totallyaslave-c4', issue=12345, patchset=654321, patch_url='http://src.chromium.org/foo/bar', fail_patch='download' ) + api.step_data('bot_update', retcode=87) yield api.test('forced') + api.properties( - mastername='experimental', - buildername='Experimental Builder', - slavename='somehost', force=1 ) yield api.test('no_shallow') + api.properties( - mastername='experimental', - buildername='Experimental Builder', - slavename='somehost', no_shallow=1 ) - yield api.test('off') + api.properties( - mastername='experimental', - buildername='Experimental Builder', - slavename='somehost', - ) - yield api.test('svn_mode') + api.properties( - mastername='experimental.svn', - buildername='Experimental SVN Builder', - slavename='somehost', - force=1 - ) yield api.test('clobber') + api.properties( - mastername='experimental', - buildername='Experimental Builder', - slavename='somehost', clobber=1 ) yield api.test('reset_root_solution_revision') + api.properties( - mastername='experimental', - buildername='Experimental Builder', - slavename='somehost', root_solution_revision='revision', ) yield api.test('tryjob_v8') + api.properties( - mastername='tryserver.chromium.linux', - buildername='linux_rel', - slavename='totallyaslave-c4', issue=12345, patchset=654321, patch_url='http://src.chromium.org/foo/bar', diff --git a/recipe_modules/bot_update/resources/bot_update.py b/recipe_modules/bot_update/resources/bot_update.py index 39c4bf61d..4ecbf6597 100755 --- a/recipe_modules/bot_update/resources/bot_update.py +++ b/recipe_modules/bot_update/resources/bot_update.py @@ -73,19 +73,18 @@ ROOT_DIR = path.dirname(BUILD_DIR) DEPOT_TOOLS_DIR = path.abspath(path.join(THIS_DIR, '..', '..', '..')) -BUILD_INTERNAL_DIR = check_dir( - 'build_internal', [ - path.join(ROOT_DIR, 'build_internal'), - path.join(ROOT_DIR, # .recipe_deps - path.pardir, # slave - path.pardir, # scripts - path.pardir), # build_internal - ]) - - CHROMIUM_GIT_HOST = 'https://chromium.googlesource.com' CHROMIUM_SRC_URL = CHROMIUM_GIT_HOST + '/chromium/src.git' +RECOGNIZED_PATHS = { + '/chrome/trunk/src': + CHROMIUM_SRC_URL, + '/chrome/trunk/src/tools/cros.DEPS': + CHROMIUM_GIT_HOST + '/chromium/src/tools/cros.DEPS.git', + '/chrome-internal/trunk/src-internal': + 'https://chrome-internal.googlesource.com/chrome/src-internal.git', +} + # Official builds use buildspecs, so this is a special case. BUILDSPEC_TYPE = collections.namedtuple('buildspec', ('container', 'version')) @@ -168,39 +167,8 @@ GOT_REVISION_MAPPINGS = { BOT_UPDATE_MESSAGE = """ -What is the "Bot Update" step? -============================== - -This step ensures that the source checkout on the bot (e.g. Chromium's src/ and -its dependencies) is checked out in a consistent state. This means that all of -the necessary repositories are checked out, no extra repositories are checked -out, and no locally modified files are present. - -These actions used to be taken care of by the "gclient revert" and "update" -steps. However, those steps are known to be buggy and occasionally flaky. This -step has two main advantages over them: - * it only operates in Git, so the logic can be clearer and cleaner; and - * it is a slave-side script, so its behavior can be modified without - restarting the master. - -Why Git, you ask? Because that is the direction that the Chromium project is -heading. This step is an integral part of the transition from using the SVN repo -at chrome/trunk/src to using the Git repo src.git. Please pardon the dust while -we fully convert everything to Git. This message will get out of your way -eventually, and the waterfall will be a happier place because of it. - -This step can be activated or deactivated independently on every builder on -every master. When it is active, the "gclient revert" and "update" steps become -no-ops. When it is inactive, it prints this message, cleans up after itself, and -lets everything else continue as though nothing has changed. Eventually, when -everything is stable enough, this step will replace them entirely. - -Debugging information: +Bot Update Debugging information: (master/builder/slave may be unspecified on recipes) -master: %(master)s -builder: %(builder)s -slave: %(slave)s -forced by recipes: %(recipe)s CURRENT_DIR: %(CURRENT_DIR)s BUILDER_DIR: %(BUILDER_DIR)s SLAVE_DIR: %(SLAVE_DIR)s @@ -208,20 +176,7 @@ THIS_DIR: %(THIS_DIR)s SCRIPTS_DIR: %(SCRIPTS_DIR)s BUILD_DIR: %(BUILD_DIR)s ROOT_DIR: %(ROOT_DIR)s -DEPOT_TOOLS_DIR: %(DEPOT_TOOLS_DIR)s -bot_update.py is:""" - -ACTIVATED_MESSAGE = """ACTIVE. -The bot will perform a Git checkout in this step. -The "gclient revert" and "update" steps are no-ops. - -""" - -NOT_ACTIVATED_MESSAGE = """INACTIVE. -This step does nothing. You actually want to look at the "update" step. - -""" - +DEPOT_TOOLS_DIR: %(DEPOT_TOOLS_DIR)s""" GCLIENT_TEMPLATE = """solutions = %(solutions)s @@ -231,137 +186,14 @@ cache_dir = r%(cache_dir)s """ -internal_data = {} -if BUILD_INTERNAL_DIR: - local_vars = {} - try: - execfile(os.path.join( - BUILD_INTERNAL_DIR, 'scripts', 'slave', 'bot_update_cfg.py'), - local_vars) - except Exception: - # Same as if BUILD_INTERNAL_DIR didn't exist in the first place. - print 'Warning: unable to read internal configuration file.' - print 'If this is an internal bot, this step may be erroneously inactive.' - internal_data = local_vars - -RECOGNIZED_PATHS = { - # If SVN path matches key, the entire URL is rewritten to the Git url. - '/chrome/trunk/src': - CHROMIUM_SRC_URL, - '/chrome/trunk/src/tools/cros.DEPS': - CHROMIUM_GIT_HOST + '/chromium/src/tools/cros.DEPS.git', -} -RECOGNIZED_PATHS.update(internal_data.get('RECOGNIZED_PATHS', {})) - -ENABLED_MASTERS = [ - 'bot_update.always_on', - 'chromium.android', - 'chromium.angle', - 'chromium.chrome', - 'chromium.chromedriver', - 'chromium.chromiumos', - 'chromium', - 'chromium.fyi', - 'chromium.goma', - 'chromium.gpu', - 'chromium.gpu.fyi', - 'chromium.infra', - 'chromium.infra.cron', - 'chromium.linux', - 'chromium.lkgr', - 'chromium.mac', - 'chromium.memory', - 'chromium.memory.fyi', - 'chromium.perf', - 'chromium.perf.fyi', - 'chromium.swarm', - 'chromium.webkit', - 'chromium.webrtc', - 'chromium.webrtc.fyi', - 'chromium.win', - 'client.catapult', - 'client.drmemory', - 'client.mojo', - 'client.nacl', - 'client.nacl.ports', - 'client.nacl.sdk', - 'client.nacl.toolchain', - 'client.pdfium', - 'client.skia', - 'client.skia.fyi', - 'client.v8', - 'client.v8.branches', - 'client.v8.fyi', - 'client.webrtc', - 'client.webrtc.fyi', - 'tryserver.blink', - 'tryserver.client.catapult', - 'tryserver.client.mojo', - 'tryserver.chromium.android', - 'tryserver.chromium.angle', - 'tryserver.chromium.linux', - 'tryserver.chromium.mac', - 'tryserver.chromium.perf', - 'tryserver.chromium.win', - 'tryserver.infra', - 'tryserver.nacl', - 'tryserver.v8', - 'tryserver.webrtc', -] -ENABLED_MASTERS += internal_data.get('ENABLED_MASTERS', []) - -ENABLED_BUILDERS = { - 'client.dart.fyi': [ - 'v8-linux-release', - 'v8-mac-release', - 'v8-win-release', - ], - 'client.dynamorio': [ - 'linux-v8-dr', - ], -} -ENABLED_BUILDERS.update(internal_data.get('ENABLED_BUILDERS', {})) - -ENABLED_SLAVES = {} -ENABLED_SLAVES.update(internal_data.get('ENABLED_SLAVES', {})) - -# Disabled filters get run AFTER enabled filters, so for example if a builder -# config is enabled, but a bot on that builder is disabled, that bot will -# be disabled. -DISABLED_BUILDERS = {} -DISABLED_BUILDERS.update(internal_data.get('DISABLED_BUILDERS', {})) - -DISABLED_SLAVES = {} -DISABLED_SLAVES.update(internal_data.get('DISABLED_SLAVES', {})) - -# These masters work only in Git, meaning for got_revision, always output -# a git hash rather than a SVN rev. -GIT_MASTERS = [ - 'client.v8', - 'client.v8.branches', - 'tryserver.v8', -] -GIT_MASTERS += internal_data.get('GIT_MASTERS', []) - - # How many times to try before giving up. ATTEMPTS = 5 -# Find deps2git -DEPS2GIT_DIR_PATH = path.join(SCRIPTS_DIR, 'tools', 'deps2git') -DEPS2GIT_PATH = path.join(DEPS2GIT_DIR_PATH, 'deps2git.py') -S2G_INTERNAL_PATH = path.join(SCRIPTS_DIR, 'tools', 'deps2git_internal', - 'svn_to_git_internal.py') GIT_CACHE_PATH = path.join(DEPOT_TOOLS_DIR, 'git_cache.py') # Find the patch tool. if sys.platform.startswith('win'): - if not BUILD_INTERNAL_DIR: - print 'Warning: could not find patch tool because there is no ' - print 'build_internal present.' - PATCH_TOOL = None - else: - PATCH_TOOL = path.join(BUILD_INTERNAL_DIR, 'tools', 'patch.EXE') + PATCH_TOOL = path.join(THIS_DIR, 'patch.EXE') else: PATCH_TOOL = '/usr/bin/patch' @@ -393,11 +225,6 @@ class InvalidDiff(Exception): pass -class Inactive(Exception): - """Not really an exception, just used to exit early cleanly.""" - pass - - RETRY = object() OK = object() FAIL = object() @@ -526,34 +353,6 @@ def get_gclient_spec(solutions, target_os, target_os_only, git_cache_dir): } -def check_enabled(master, builder, slave): - if master in ENABLED_MASTERS: - return True - builder_list = ENABLED_BUILDERS.get(master) - if builder_list and builder in builder_list: - return True - slave_list = ENABLED_SLAVES.get(master) - if slave_list and slave in slave_list: - return True - return False - - -def check_disabled(master, builder, slave): - """Returns True if disabled, False if not disabled.""" - builder_list = DISABLED_BUILDERS.get(master) - if builder_list and builder in builder_list: - return True - slave_list = DISABLED_SLAVES.get(master) - if slave_list and slave in slave_list: - return True - return False - - -def check_valid_host(master, builder, slave): - return (check_enabled(master, builder, slave) - and not check_disabled(master, builder, slave)) - - def maybe_ignore_revision(revision, buildspec): """Handle builders that don't care what buildbot tells them to build. @@ -781,17 +580,6 @@ def get_commit_message_footer(message, key): return get_commit_message_footer_map(message).get(key) -def get_svn_rev(git_hash, dir_name): - log = git('log', '-1', git_hash, cwd=dir_name) - git_svn_id = get_commit_message_footer(log, GIT_SVN_ID_FOOTER_KEY) - if not git_svn_id: - return None - m = GIT_SVN_ID_RE.match(git_svn_id) - if not m: - return None - return int(m.group(2)) - - def get_git_hash(revision, branch, sln_dir): """We want to search for the SVN revision on the git-svn branch. @@ -807,59 +595,6 @@ def get_git_hash(revision, branch, sln_dir): (revision, sln_dir)) -def _last_commit_for_file(filename, repo_base): - cmd = ['log', '--format=%H', '--max-count=1', '--', filename] - return git(*cmd, cwd=repo_base).strip() - - -def need_to_run_deps2git(repo_base, deps_file, deps_git_file): - """Checks to see if we need to run deps2git. - - Returns True if there was a DEPS change after the last .DEPS.git update - or if DEPS has local modifications. - """ - # See if DEPS is dirty - deps_file_status = git( - 'status', '--porcelain', deps_file, cwd=repo_base).strip() - if deps_file_status and deps_file_status.startswith('M '): - return True - - last_known_deps_ref = _last_commit_for_file(deps_file, repo_base) - last_known_deps_git_ref = _last_commit_for_file(deps_git_file, repo_base) - merge_base_ref = git('merge-base', last_known_deps_ref, - last_known_deps_git_ref, cwd=repo_base).strip() - - # If the merge base of the last DEPS and last .DEPS.git file is not - # equivilent to the hash of the last DEPS file, that means the DEPS file - # was committed after the last .DEPS.git file. - return last_known_deps_ref != merge_base_ref - - -def ensure_deps2git(solution, shallow, git_cache_dir): - repo_base = path.join(os.getcwd(), solution['name']) - deps_file = path.join(repo_base, 'DEPS') - deps_git_file = path.join(repo_base, '.DEPS.git') - if (not git('ls-files', 'DEPS', cwd=repo_base).strip() or - not git('ls-files', '.DEPS.git', cwd=repo_base).strip()): - return - - print 'Checking if %s is newer than %s' % (deps_file, deps_git_file) - if not need_to_run_deps2git(repo_base, deps_file, deps_git_file): - return - - print '===DEPS file modified, need to run deps2git===' - cmd = [sys.executable, DEPS2GIT_PATH, - '--workspace', os.getcwd(), - '--cache_dir', git_cache_dir, - '--deps', deps_file, - '--out', deps_git_file] - if 'chrome-internal.googlesource' in solution['url']: - cmd.extend(['--extra-rules', S2G_INTERNAL_PATH]) - if shallow: - cmd.append('--shallow') - call(*cmd) - - def emit_log_lines(name, lines): for line in lines.splitlines(): print '@@@STEP_LOG_LINE@%s@%s@@@' % (name, line) @@ -925,6 +660,7 @@ def force_revision(folder_name, revision): ref = branch if branch.startswith('refs/') else 'origin/%s' % branch git('checkout', '--force', ref, cwd=folder_name) + def git_checkout(solutions, revisions, shallow, refs, git_cache_dir): build_dir = os.getcwd() # Before we do anything, break all git_cache locks. @@ -985,16 +721,6 @@ def git_checkout(solutions, revisions, shallow, refs, git_cache_dir): else: raise remove(sln_dir) - except SVNRevisionNotFound: - tries_left -= 1 - if tries_left > 0: - # If we don't have the correct revision, wait and try again. - print 'We can\'t find revision %s.' % revision - print 'The svn to git replicator is probably falling behind.' - print 'waiting 5 seconds and trying again...' - time.sleep(5) - else: - raise git('clean', '-dff', cwd=sln_dir) @@ -1005,16 +731,6 @@ def git_checkout(solutions, revisions, shallow, refs, git_cache_dir): return git_ref -def _download(url): - """Fetch url and return content, with retries for flake.""" - for attempt in xrange(ATTEMPTS): - try: - return urllib2.urlopen(url).read() - except Exception: - if attempt == ATTEMPTS - 1: - raise - - def parse_diff(diff): """Takes a unified diff and returns a list of diffed files and their diffs. @@ -1224,12 +940,8 @@ def get_commit_position(git_path, revision='HEAD'): return None -def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs): - """Translate git gclient revision mapping to build properties. - - If use_svn_revs is True, then translate git hashes in the revision mapping - to svn revision numbers. - """ +def parse_got_revision(gclient_output, got_revision_mapping): + """Translate git gclient revision mapping to build properties.""" properties = {} solutions_output = { # Make sure path always ends with a single slash. @@ -1249,12 +961,7 @@ def parse_got_revision(gclient_output, got_revision_mapping, use_svn_revs): # Since we are using .DEPS.git, everything had better be git. assert solution_output.get('scm') == 'git' git_revision = git('rev-parse', 'HEAD', cwd=dir_name).strip() - if use_svn_revs: - revision = get_svn_rev(git_revision, dir_name) - if not revision: - revision = git_revision - else: - revision = git_revision + revision = git_revision commit_position = get_commit_position(dir_name) properties[property_name] = revision @@ -1286,7 +993,6 @@ def ensure_deps_revisions(deps_url_mapping, solutions, revisions): revisions) if not revision: continue - # TODO(hinoka): Catch SVNRevisionNotFound error maybe? git('fetch', 'origin', cwd=deps_name) force_revision(deps_name, revision) @@ -1323,11 +1029,6 @@ def ensure_checkout(solutions, revisions, first_sln, target_os, target_os_only, apply_issue_key_file, whitelist=[target]) already_patched.append(target) - if not buildspec: - # Run deps2git if there is a DEPS change after the last .DEPS.git commit. - for solution in solutions: - ensure_deps2git(solution, shallow, git_cache_dir) - # Ensure our build/ directory is set up with the correct .gclient file. gclient_configure(solutions, target_os, target_os_only, git_cache_dir) @@ -1431,8 +1132,6 @@ def parse_args(): help='--private-key-file option passthrough for ' 'apply_patch.py.') parse.add_option('--patch_url', help='Optional URL to SVN patch.') - parse.add_option('--root', dest='patch_root', - help='DEPRECATED: Use --patch_root.') parse.add_option('--patch_root', help='Directory to patch on top of.') parse.add_option('--rietveld_server', default='codereview.chromium.org', @@ -1441,10 +1140,6 @@ def parse_args(): help='Gerrit repository to pull the ref from.') parse.add_option('--gerrit_ref', help='Gerrit ref to apply.') parse.add_option('--specs', help='Gcilent spec.') - parse.add_option('--master', help='Master name.') - parse.add_option('-f', '--force', action='store_true', - help='Bypass check to see if we want to be run. ' - 'Should ONLY be used locally or by smart recipes.') parse.add_option('--revision_mapping', help='{"path/to/repo/": "property_name"}') parse.add_option('--revision_mapping_file', @@ -1460,11 +1155,6 @@ def parse_args(): 'set to :.') parse.add_option('--output_manifest', action='store_true', help=('Add manifest json to the json output.')) - parse.add_option('--slave_name', default=socket.getfqdn().split('.')[0], - help='Hostname of the current machine, ' - 'used for determining whether or not to activate.') - parse.add_option('--builder_name', help='Name of the builder, ' - 'used for determining whether or not to activate.') parse.add_option('--build_dir', default=os.getcwd()) parse.add_option('--flag_file', default=path.join(os.getcwd(), 'update.flag')) @@ -1526,25 +1216,17 @@ def parse_args(): return options, args -def prepare(options, git_slns, active): +def prepare(options, git_slns): """Prepares the target folder before we checkout.""" dir_names = [sln.get('name') for sln in git_slns if 'name' in sln] - # If we're active now, but the flag file doesn't exist (we weren't active - # last run) or vice versa, blow away all checkouts. - if bool(active) != bool(check_flag(options.flag_file)): - ensure_no_checkout(dir_names, '*') if options.output_json: # Make sure we tell recipes that we didn't run if the script exits here. - emit_json(options.output_json, did_run=active) - if active: - if options.clobber: - ensure_no_checkout(dir_names, '*') - else: - ensure_no_checkout(dir_names, '.svn') - emit_flag(options.flag_file) + emit_json(options.output_json, did_run=True) + if options.clobber: + ensure_no_checkout(dir_names, '*') else: - delete_flag(options.flag_file) - raise Inactive # This is caught in main() and we exit cleanly. + ensure_no_checkout(dir_names, '.svn') + emit_flag(options.flag_file) # Do a shallow checkout if the disk is less than 100GB. total_disk_space, free_disk_space = get_total_disk_space() @@ -1571,7 +1253,7 @@ def prepare(options, git_slns, active): return revisions, step_text -def checkout(options, git_slns, specs, buildspec, master, +def checkout(options, git_slns, specs, buildspec, svn_root, revisions, step_text): first_sln = git_slns[0]['name'] dir_names = [sln.get('name') for sln in git_slns if 'name' in sln] @@ -1633,9 +1315,6 @@ def checkout(options, git_slns, specs, buildspec, master, print '@@@STEP_TEXT@%s PATCH FAILED@@@' % step_text raise - # Revision is an svn revision, unless it's a git master. - use_svn_rev = master not in GIT_MASTERS - # Take care of got_revisions outputs. revision_mapping = dict(GOT_REVISION_MAPPINGS.get(svn_root, {})) if options.revision_mapping: @@ -1647,8 +1326,7 @@ def checkout(options, git_slns, specs, buildspec, master, if not revision_mapping: revision_mapping[first_sln] = 'got_revision' - got_revisions = parse_got_revision(gclient_output, revision_mapping, - use_svn_rev) + got_revisions = parse_got_revision(gclient_output, revision_mapping) if not got_revisions: # TODO(hinoka): We should probably bail out here, but in the interest @@ -1673,22 +1351,9 @@ def checkout(options, git_slns, specs, buildspec, master, emit_properties(got_revisions) -def print_help_text(force, output_json, active, master, builder, slave): +def print_help_text(master, builder, slave): """Print helpful messages to tell devs whats going on.""" - if force and output_json: - recipe_force = 'Forced on by recipes' - elif active and output_json: - recipe_force = 'Off by recipes, but forced on by bot update' - elif not active and output_json: - recipe_force = 'Forced off by recipes' - else: - recipe_force = 'N/A. Was not called by recipes' - print BOT_UPDATE_MESSAGE % { - 'master': master or 'Not specified', - 'builder': builder or 'Not specified', - 'slave': slave or 'Not specified', - 'recipe': recipe_force, 'CURRENT_DIR': CURRENT_DIR, 'BUILDER_DIR': BUILDER_DIR, 'SLAVE_DIR': SLAVE_DIR, @@ -1698,7 +1363,6 @@ def print_help_text(force, output_json, active, master, builder, slave): 'ROOT_DIR': ROOT_DIR, 'DEPOT_TOOLS_DIR': DEPOT_TOOLS_DIR, }, - print ACTIVATED_MESSAGE if active else NOT_ACTIVATED_MESSAGE def main(): @@ -1708,12 +1372,8 @@ def main(): slave = options.slave_name master = options.master - # Check if this script should activate or not. - active = check_valid_host(master, builder, slave) or options.force or False - - # Print a helpful message to tell developers whats going on with this step. - print_help_text( - options.force, options.output_json, active, master, builder, slave) + # Prints some debugging information. + print_help_text(master, builder, slave) # Parse, munipulate, and print the gclient solutions. specs = {} @@ -1726,13 +1386,10 @@ def main(): try: # Dun dun dun, the main part of bot_update. - revisions, step_text = prepare(options, git_slns, active) - checkout(options, git_slns, specs, buildspec, master, svn_root, revisions, + revisions, step_text = prepare(options, git_slns) + checkout(options, git_slns, specs, buildspec, svn_root, revisions, step_text) - except Inactive: - # Not active, should count as passing. - pass except PatchFailed as e: emit_flag(options.flag_file) # Return a specific non-zero exit code for patch failure (because it is diff --git a/recipe_modules/bot_update/resources/patch.exe b/recipe_modules/bot_update/resources/patch.exe new file mode 100755 index 0000000000000000000000000000000000000000..b8eca116755c312a7842ce9fbb710f8084e08325 GIT binary patch literal 112640 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 K=P&s9^7ua_+%>rX literal 0 HcmV?d00001 diff --git a/recipe_modules/bot_update/test_api.py b/recipe_modules/bot_update/test_api.py index 64eab8ab1..97fac0852 100644 --- a/recipe_modules/bot_update/test_api.py +++ b/recipe_modules/bot_update/test_api.py @@ -14,73 +14,61 @@ import bot_update class BotUpdateTestApi(recipe_test_api.RecipeTestApi): - def output_json(self, master, builder, slave, root, first_sln, - revision_mapping, git_mode, force=False, fail_patch=False, + def output_json(self, root, first_sln, revision_mapping, fail_patch=False, output_manifest=False, fixed_revisions=None): """Deterministically synthesize json.output test data for gclient's --output-json option. """ - active = bot_update.check_valid_host(master, builder, slave) or force output = { - 'did_run': active, + 'did_run': True, 'patch_failure': False } - # Add in extra json output if active. - if active: - properties = { - property_name: self.gen_revision(project_name, git_mode) - for project_name, property_name in revision_mapping.iteritems() - } - properties.update({ - '%s_cp' % property_name: ('refs/heads/master@{#%s}' % - self.gen_revision(project_name, False)) - for project_name, property_name in revision_mapping.iteritems() - }) + properties = { + property_name: self.gen_revision(project_name, True) + for project_name, property_name in revision_mapping.iteritems() + } + properties.update({ + '%s_cp' % property_name: ('refs/heads/master@{#%s}' % + self.gen_revision(project_name, False)) + for project_name, property_name in revision_mapping.iteritems() + }) - # We also want to simulate outputting "got_revision_git": ... - # when git mode is off to match what bot_update.py does. - if not git_mode: - properties.update({ - '%s_git' % property_name: self.gen_revision(project_name, True) - for project_name, property_name in revision_mapping.iteritems() - }) + output.update({ + 'patch_root': root or first_sln, + 'root': first_sln, + 'properties': properties, + 'step_text': 'Some step text' + }) + if output_manifest: output.update({ - 'patch_root': root or first_sln, - 'root': first_sln, - 'properties': properties, - 'step_text': 'Some step text' - }) - - if output_manifest: - output.update({ - 'manifest': { - project_name: { - 'repository': 'https://fake.org/%s.git' % project_name, - 'revision': self.gen_revision(project_name, git_mode), - } - for project_name in revision_mapping + 'manifest': { + project_name: { + 'repository': 'https://fake.org/%s.git' % project_name, + 'revision': self.gen_revision(project_name, True), } - }) + for project_name in revision_mapping + } + }) - if fixed_revisions: - output['fixed_revisions'] = fixed_revisions + if fixed_revisions: + output['fixed_revisions'] = fixed_revisions - if fail_patch: - output['log_lines'] = [('patch error', 'Patch failed to apply'),] - output['patch_failure'] = True - output['patch_apply_return_code'] = 1 - if fail_patch == 'download': - output['patch_apply_return_code'] = 3 + if fail_patch: + output['log_lines'] = [('patch error', 'Patch failed to apply'),] + output['patch_failure'] = True + output['patch_apply_return_code'] = 1 + if fail_patch == 'download': + output['patch_apply_return_code'] = 3 return self.m.json.output(output) @staticmethod - def gen_revision(project, GIT_MODE): + def gen_revision(project, git_mode): """Hash project to bogus deterministic revision values.""" h = hashlib.sha1(project) - if GIT_MODE: + if git_mode: return h.hexdigest() else: return struct.unpack('!I', h.digest()[:4])[0] % 300000 diff --git a/tests/bot_update_coverage_test.py b/tests/bot_update_coverage_test.py new file mode 100755 index 000000000..19adfd2f8 --- /dev/null +++ b/tests/bot_update_coverage_test.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python +# Copyright (c) 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import codecs +import copy +import json +import os +import sys +import unittest + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +RESOURCE_DIR = os.path.join( + ROOT_DIR, 'recipe_modules', 'bot_update', 'resources') + +sys.path.insert(0, RESOURCE_DIR) +sys.platform = 'linux2' # For consistency, ya know? +import bot_update + +DEFAULT_PARAMS = { + 'solutions': [{ + 'name': 'somename', + 'url': 'https://fake.com' + }], + 'revisions': [], + 'first_sln': 'somename', + 'target_os': None, + 'target_os_only': None, + 'patch_root': None, + 'issue': None, + 'patchset': None, + 'patch_url': None, + 'rietveld_server': None, + 'gerrit_ref': None, + 'gerrit_repo': None, + 'revision_mapping': {}, + 'apply_issue_email_file': None, + 'apply_issue_key_file': None, + 'buildspec': False, + 'gyp_env': None, + 'shallow': False, + 'runhooks': False, + 'refs': [], + 'git_cache_dir': 'fake_cache_dir' +} + + +class MockedPopen(object): + """A fake instance of a called subprocess. + + This is meant to be used in conjunction with MockedCall. + """ + def __init__(self, args=None, kwargs=None): + self.args = args or [] + self.kwargs = kwargs or {} + self.return_value = None + self.fails = False + + def returns(self, rv): + """Set the return value when this popen is called. + + rv can be a string, or a callable (eg function). + """ + self.return_value = rv + return self + + def check(self, args, kwargs): + """Check to see if the given args/kwargs call match this instance. + + This does a partial match, so that a call to "git clone foo" will match + this instance if this instance was recorded as "git clone" + """ + if any(input_arg != expected_arg + for (input_arg, expected_arg) in zip(args, self.args)): + return False + return self.return_value + + def __call__(self, args, kwargs): + """Actually call this popen instance.""" + if hasattr(self.return_value, '__call__'): + return self.return_value(*args, **kwargs) + return self.return_value + + +class MockedCall(object): + """A fake instance of bot_update.call(). + + This object is pre-seeded with "answers" in self.expectations. The type + is a MockedPopen object, or any object with a __call__() and check() method. + The check() method is used to check to see if the correct popen object is + chosen (can be a partial match, eg a "git clone" popen module would match + a "git clone foo" call). + By default, if no answers have been pre-seeded, the call() returns successful + with an empty string. + """ + def __init__(self, fake_filesystem): + self.expectations = [] + self.records = [] + + def expect(self, args=None, kwargs=None): + args = args or [] + kwargs = kwargs or {} + popen = MockedPopen(args, kwargs) + self.expectations.append(popen) + return popen + + def __call__(self, *args, **kwargs): + self.records.append((args, kwargs)) + for popen in self.expectations: + if popen.check(args, kwargs): + self.expectations.remove(popen) + return popen(args, kwargs) + return '' + + +class MockedGclientSync(): + """A class producing a callable instance of gclient sync. + + Because for bot_update, gclient sync also emits an output json file, we need + a callable object that can understand where the output json file is going, and + emit a (albite) fake file for bot_update to consume. + """ + def __init__(self, fake_filesystem): + self.output = {} + self.fake_filesystem = fake_filesystem + + def __call__(self, *args, **_): + output_json_index = args.index('--output-json') + 1 + with self.fake_filesystem.open(args[output_json_index], 'w') as f: + json.dump(self.output, f) + + +class FakeFile(): + def __init__(self): + self.contents = '' + + def write(self, buf): + self.contents += buf + + def read(self): + return self.contents + + def __enter__(self): + return self + + def __exit__(self, _, __, ___): + pass + + +class FakeFilesystem(): + def __init__(self): + self.files = {} + + def open(self, target, mode='r', encoding=None): + if 'w' in mode: + self.files[target] = FakeFile() + return self.files[target] + return self.files[target] + + +def fake_git(*args, **kwargs): + return bot_update.call('git', *args, **kwargs) + + +class EnsureCheckoutUnittests(unittest.TestCase): + def setUp(self): + self.filesystem = FakeFilesystem() + self.call = MockedCall(self.filesystem) + self.gclient = MockedGclientSync(self.filesystem) + self.call.expect(('gclient', 'sync')).returns(self.gclient) + self.old_call = getattr(bot_update, 'call') + self.params = copy.deepcopy(DEFAULT_PARAMS) + setattr(bot_update, 'call', self.call) + setattr(bot_update, 'git', fake_git) + + self.old_os_cwd = os.getcwd + setattr(os, 'getcwd', lambda: '/b/build/slave/foo/build') + + setattr(bot_update, 'open', self.filesystem.open) + self.old_codecs_open = codecs.open + setattr(codecs, 'open', self.filesystem.open) + + def tearDown(self): + setattr(bot_update, 'call', self.old_call) + setattr(os, 'getcwd', self.old_os_cwd) + delattr(bot_update, 'open') + setattr(codecs, 'open', self.old_codecs_open) + + def testBasic(self): + bot_update.ensure_checkout(**self.params) + return self.call.records + + def testBasicBuildspec(self): + self.params['buildspec'] = bot_update.BUILDSPEC_TYPE( + container='branches', + version='1.1.1.1' + ) + bot_update.ensure_checkout(**self.params) + return self.call.records + + def testBasicShallow(self): + self.params['shallow'] = True + bot_update.ensure_checkout(**self.params) + return self.call.records + + def testBasicSVNPatch(self): + self.params['patch_url'] = 'svn://server.com/patch.diff' + self.params['patch_root'] = 'somename' + bot_update.ensure_checkout(**self.params) + return self.call.records + + def testBasicRietveldPatch(self): + self.params['issue'] = '12345' + self.params['patchset'] = '1' + self.params['rietveld_server'] = 'https://rietveld.com' + self.params['patch_root'] = 'somename' + bot_update.ensure_checkout(**self.params) + return self.call.records + + +class SolutionsUnittests(unittest.TestCase): + def testBasicSVN(self): + sln = [{ + 'name': 'src', + 'url': 'svn://svn-mirror.golo.chromium.org/chrome/trunk/src' + }, { + 'name': 'git', + 'url': 'https://abc.googlesource.com/foo.git' + }] + git_slns, root, buildspec = bot_update.solutions_to_git(sln) + self.assertEquals(root, '/chrome/trunk/src') + self.assertEquals(git_slns, [{ + 'deps_file': '.DEPS.git', + 'managed': False, + 'name': 'src', + 'url': 'https://chromium.googlesource.com/chromium/src.git' + }, { + 'url': 'https://abc.googlesource.com/foo.git', + 'managed': False, + 'name': 'git' + }]) + self.assertFalse(buildspec) + bot_update.solutions_printer(git_slns) + + def testBasicBuildspec(self): + sln = [{ + 'name': 'buildspec', + 'url': ('svn://svn.chromium.org/chrome-internal/' + 'trunk/tools/buildspec/releases/1234'), + }] + git_slns, root, buildspec = bot_update.solutions_to_git(sln) + self.assertEquals( + buildspec, + bot_update.BUILDSPEC_TYPE(container='releases', version='1234')) + self.assertEquals( + root, '/chrome-internal/trunk/tools/buildspec/releases/1234') + self.assertEquals(git_slns[0]['deps_file'], 'releases/1234/DEPS') + +if __name__ == '__main__': + unittest.main()