Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """A tool to archive layout test results. | 6 """A tool to archive layout test results. |
| 7 | 7 |
| 8 To archive files on Google Storage, pass a GS bucket name via --gs-bucket. | 8 To archive files on Google Storage, pass a GS bucket name via --gs-bucket. |
| 9 To control access to archives, pass a value for --gs-acl (e.g. 'public-read', | 9 To control access to archives, pass a value for --gs-acl (e.g. 'public-read', |
| 10 see https://developers.google.com/storage/docs/accesscontrol#extension | 10 see https://developers.google.com/storage/docs/accesscontrol#extension |
| 11 for other supported canned-acl values). If no gs_acl key is given, | 11 for other supported canned-acl values). If no gs_acl key is given, |
| 12 then the bucket's default object ACL will be applied (see | 12 then the bucket's default object ACL will be applied (see |
| 13 https://developers.google.com/storage/docs/accesscontrol#defaultobjects). | 13 https://developers.google.com/storage/docs/accesscontrol#defaultobjects). |
| 14 | 14 |
| 15 When this is run, the current directory (cwd) should be the outer build | 15 When this is run, the current directory (cwd) should be the outer build |
| 16 directory (e.g., chrome-release/build/). | 16 directory (e.g., chrome-release/build/). |
| 17 | 17 |
| 18 For a list of command-line options, call this script with '--help'. | 18 For a list of command-line options, call this script with '--help'. |
| 19 """ | 19 """ |
| 20 | 20 |
| 21 import logging | 21 import logging |
| 22 import optparse | 22 import argparse |
| 23 import os | 23 import os |
| 24 import re | 24 import re |
| 25 import socket | 25 import socket |
| 26 import sys | 26 import sys |
| 27 | 27 |
| 28 from common import archive_utils | 28 from common import archive_utils |
| 29 from common import chromium_utils | 29 from common import chromium_utils |
| 30 from slave import build_directory | 30 from slave import build_directory |
| 31 from slave import slave_utils | 31 from slave import slave_utils |
| 32 | 32 |
| 33 # Directory name, above the build directory, in which test results can be | |
| 34 # found if no --results-dir option is given. | |
| 35 RESULT_DIR = 'layout-test-results' | |
| 36 | |
| 37 | 33 |
| 38 def _CollectArchiveFiles(output_dir): | 34 def _CollectArchiveFiles(output_dir): |
| 39 """Returns a list of actual layout test result files to archive.""" | 35 """Returns a list of actual layout test result files to archive.""" |
| 40 actual_file_list = [] | 36 actual_file_list = [] |
| 41 | 37 |
| 42 for path, _, files in os.walk(output_dir): | 38 for path, _, files in os.walk(output_dir): |
| 43 rel_path = path[len(output_dir + '\\'):] | 39 rel_path = path[len(output_dir + '\\'):] |
| 44 for name in files: | 40 for name in files: |
| 45 if _IsActualResultFile(name): | 41 if _IsActualResultFile(name): |
| 46 actual_file_list.append(os.path.join(rel_path, name)) | 42 actual_file_list.append(os.path.join(rel_path, name)) |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 60 | 56 |
| 61 | 57 |
| 62 def _IsActualResultFile(name): | 58 def _IsActualResultFile(name): |
| 63 if '-stack.' in name or '-crash-log.' in name: | 59 if '-stack.' in name or '-crash-log.' in name: |
| 64 return True | 60 return True |
| 65 extension = os.path.splitext(name)[1] | 61 extension = os.path.splitext(name)[1] |
| 66 return ('-actual.' in name and extension in | 62 return ('-actual.' in name and extension in |
| 67 ('.txt', '.png', '.checksum', '.wav')) | 63 ('.txt', '.png', '.checksum', '.wav')) |
| 68 | 64 |
| 69 | 65 |
| 70 def archive_layout(options): | 66 def archive_layout(args): |
| 71 chrome_dir = os.path.abspath(options.build_dir) | 67 chrome_dir = os.path.abspath(args.build_dir) |
| 72 results_dir_basename = os.path.basename(options.results_dir) | 68 results_dir_basename = os.path.basename(args.results_dir) |
| 73 if options.results_dir is not None: | 69 args.results_dir = os.path.abspath(args.results_dir) |
| 74 options.results_dir = os.path.abspath(os.path.join(options.build_dir, | 70 print 'Archiving results from %s' % args.results_dir |
| 75 options.results_dir)) | 71 staging_dir = args.staging_dir or slave_utils.GetStagingDir(chrome_dir) |
| 76 else: | |
| 77 options.results_dir = chromium_utils.FindUpward(chrome_dir, RESULT_DIR) | |
| 78 print 'Archiving results from %s' % options.results_dir | |
| 79 staging_dir = options.staging_dir or slave_utils.GetStagingDir(chrome_dir) | |
| 80 print 'Staging in %s' % staging_dir | 72 print 'Staging in %s' % staging_dir |
| 81 if not os.path.exists(staging_dir): | 73 if not os.path.exists(staging_dir): |
| 82 os.makedirs(staging_dir) | 74 os.makedirs(staging_dir) |
| 83 | 75 |
| 84 actual_file_list = _CollectArchiveFiles(options.results_dir) | 76 actual_file_list = _CollectArchiveFiles(args.results_dir) |
| 85 zip_file = chromium_utils.MakeZip(staging_dir, | 77 zip_file = chromium_utils.MakeZip(staging_dir, |
| 86 results_dir_basename, | 78 results_dir_basename, |
| 87 actual_file_list, | 79 actual_file_list, |
| 88 options.results_dir)[1] | 80 args.results_dir)[1] |
| 89 | |
| 90 # Extract the build name of this slave (e.g., 'chrome-release') from its | |
| 91 # configuration file if not provided as a param. | |
| 92 build_name = options.builder_name or slave_utils.SlaveBuildName(chrome_dir) | |
| 93 build_name = re.sub('[ .()]', '_', build_name) | |
| 94 | 81 |
| 95 wc_dir = os.path.dirname(chrome_dir) | 82 wc_dir = os.path.dirname(chrome_dir) |
| 96 last_change = slave_utils.GetHashOrRevision(wc_dir) | 83 last_change = slave_utils.GetHashOrRevision(wc_dir) |
| 97 | 84 |
| 98 # TODO(dpranke): Is it safe to assume build_number is not blank? Should we | 85 builder_name = re.sub('[ .()]', '_', args.builder_name) |
| 99 # assert() this ? | 86 build_number = str(args.build_number) |
| 100 build_number = str(options.build_number) | 87 |
| 101 print 'last change: %s' % last_change | 88 print 'last change: %s' % last_change |
| 102 print 'build name: %s' % build_name | 89 print 'build name: %s' % builder_name |
| 103 print 'build number: %s' % build_number | 90 print 'build number: %s' % build_number |
| 104 print 'host name: %s' % socket.gethostname() | 91 print 'host name: %s' % socket.gethostname() |
| 105 | 92 |
| 106 # Create a file containing last_change revision. This file will be uploaded | 93 # Create a file containing last_change revision. This file will be uploaded |
| 107 # after all layout test results are uploaded so the client can check this | 94 # after all layout test results are uploaded so the client can check this |
| 108 # file to see if the upload for the revision is complete. | 95 # file to see if the upload for the revision is complete. |
| 109 # See crbug.com/574272 for more details. | 96 # See crbug.com/574272 for more details. |
| 110 last_change_file = os.path.join(staging_dir, 'LAST_CHANGE') | 97 last_change_file = os.path.join(staging_dir, 'LAST_CHANGE') |
| 111 with open(last_change_file, 'w') as f: | 98 with open(last_change_file, 'w') as f: |
| 112 f.write(last_change) | 99 f.write(last_change) |
| 113 | 100 |
| 114 # Copy the results to a directory archived by build number. | 101 # Copy the results to a directory archived by build number. |
| 115 gs_base = '/'.join([options.gs_bucket, build_name, build_number]) | 102 gs_base = '/'.join([args.gs_bucket, builder_name, build_number]) |
| 116 gs_acl = options.gs_acl | 103 gs_acl = args.gs_acl |
| 117 # These files never change, cache for a year. | 104 # These files never change, cache for a year. |
| 118 cache_control = "public, max-age=31556926" | 105 cache_control = "public, max-age=31556926" |
| 119 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, | 106 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, |
| 120 cache_control=cache_control) | 107 cache_control=cache_control) |
| 121 slave_utils.GSUtilCopyDir(options.results_dir, gs_base, gs_acl=gs_acl, | 108 slave_utils.GSUtilCopyDir(args.results_dir, gs_base, gs_acl=gs_acl, |
| 122 cache_control=cache_control) | 109 cache_control=cache_control) |
| 123 slave_utils.GSUtilCopyFile(last_change_file, | 110 slave_utils.GSUtilCopyFile(last_change_file, |
| 124 gs_base + '/' + results_dir_basename, | 111 gs_base + '/' + results_dir_basename, |
| 125 gs_acl=gs_acl, | 112 gs_acl=gs_acl, |
| 126 cache_control=cache_control) | 113 cache_control=cache_control) |
| 127 | 114 |
| 128 # And also to the 'results' directory to provide the 'latest' results | 115 # And also to the 'results' directory to provide the 'latest' results |
| 129 # and make sure they are not cached at all (Cloud Storage defaults to | 116 # and make sure they are not cached at all (Cloud Storage defaults to |
| 130 # caching w/ a max-age=3600). | 117 # caching w/ a max-age=3600). |
| 131 gs_base = '/'.join([options.gs_bucket, build_name, 'results']) | 118 gs_base = '/'.join([args.gs_bucket, builder_name, 'results']) |
| 132 cache_control = 'no-cache' | 119 cache_control = 'no-cache' |
| 133 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, | 120 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, |
| 134 cache_control=cache_control) | 121 cache_control=cache_control) |
| 135 slave_utils.GSUtilCopyDir(options.results_dir, gs_base, gs_acl=gs_acl, | 122 slave_utils.GSUtilCopyDir(args.results_dir, gs_base, gs_acl=gs_acl, |
| 136 cache_control=cache_control) | 123 cache_control=cache_control) |
| 137 slave_utils.GSUtilCopyFile(last_change_file, | 124 slave_utils.GSUtilCopyFile(last_change_file, |
| 138 gs_base + '/' + results_dir_basename, | 125 gs_base + '/' + results_dir_basename, |
| 139 gs_acl=gs_acl, | 126 gs_acl=gs_acl, |
| 140 cache_control=cache_control) | 127 cache_control=cache_control) |
| 141 return 0 | 128 return 0 |
| 142 | 129 |
| 143 | 130 |
| 144 def _ParseOptions(): | 131 def _ParseArgs(): |
| 145 option_parser = optparse.OptionParser() | 132 parser = argparse.ArgumentParser() |
| 146 option_parser.add_option('', '--build-dir', help='ignored') | 133 # TODO(crbug.com/655798): Make --build-dir not ignored. |
| 147 option_parser.add_option('', '--results-dir', | 134 parser.add_argument('--build-dir', help='ignored') |
| 148 help='path to layout test results, relative to ' | 135 parser.add_argument('--results-dir', required=True, |
|
Dirk Pranke
2016/10/18 20:03:03
Nice, didn't know about the required flag.
| |
| 149 'the build_dir') | 136 help='path to layout test results') |
| 150 option_parser.add_option('', '--builder-name', | 137 parser.add_argument('--builder-name', required=True, |
| 151 default=None, | 138 help='The name of the builder running this script.') |
| 152 help='The name of the builder running this script.') | 139 parser.add_argument('--build-number', type=int, required=True, |
| 153 option_parser.add_option('', '--build-number', | 140 help='Build number of the builder running this script.') |
| 154 default=None, | 141 parser.add_argument('--gs-bucket', required=True, |
| 155 help=('The build number of the builder running' | 142 help='The Google Storage bucket to upload to.') |
| 156 'this script.')) | 143 parser.add_argument('--gs-acl', |
| 157 option_parser.add_option('', '--gs-bucket', | 144 help='The access policy for Google Storage files.') |
| 158 default=None, | 145 parser.add_argument('--staging-dir', |
| 159 help=('The google storage bucket to upload to. ' | 146 help='Directory to use for staging the archives. ' |
| 160 'If provided, this script will upload to gs ' | 147 'Default behavior is to automatically detect ' |
| 161 'instead of the master.')) | 148 'slave\'s build directory.') |
| 162 option_parser.add_option('', '--gs-acl', | 149 args = parser.parse_args() |
| 163 default=None, | 150 args.build_dir = build_directory.GetBuildOutputDirectory() |
| 164 help=('The ACL of the google storage files.')) | 151 return args |
| 165 option_parser.add_option('--staging-dir', | |
| 166 help='Directory to use for staging the archives. ' | |
| 167 'Default behavior is to automatically detect ' | |
| 168 'slave\'s build directory.') | |
| 169 chromium_utils.AddPropertiesOptions(option_parser) | |
| 170 options, _ = option_parser.parse_args() | |
| 171 if not options.gs_bucket: | |
| 172 option_parser.error('--gs-bucket is required.') | |
| 173 options.build_dir = build_directory.GetBuildOutputDirectory() | |
| 174 return options | |
| 175 | 152 |
| 176 | 153 |
| 177 def main(): | 154 def main(): |
| 178 options = _ParseOptions() | 155 args = _ParseArgs() |
| 179 logging.basicConfig(level=logging.INFO, | 156 logging.basicConfig(level=logging.INFO, |
| 180 format='%(asctime)s %(filename)s:%(lineno)-3d' | 157 format='%(asctime)s %(filename)s:%(lineno)-3d' |
| 181 ' %(levelname)s %(message)s', | 158 ' %(levelname)s %(message)s', |
| 182 datefmt='%y%m%d %H:%M:%S') | 159 datefmt='%y%m%d %H:%M:%S') |
| 183 return archive_layout(options) | 160 return archive_layout(args) |
| 184 | 161 |
| 185 | 162 |
| 186 if '__main__' == __name__: | 163 if '__main__' == __name__: |
| 187 sys.exit(main()) | 164 sys.exit(main()) |
| OLD | NEW |