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 generated by buildbots. | 6 """A tool to archive layout test results generated by buildbots. |
| 7 | 7 |
| 8 Actual result files (*-actual.txt), but not results from simplified diff | 8 Actual result files (*-actual.txt), but not results from simplified diff |
| 9 tests (*-simp-actual.txt) or JS-filtered diff tests (*-jsfilt.txt), will | 9 tests (*-simp-actual.txt) or JS-filtered diff tests (*-jsfilt.txt), will |
| 10 be included in the archive. | 10 be included in the archive. |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 33 from common import chromium_utils | 33 from common import chromium_utils |
| 34 from slave import build_directory | 34 from slave import build_directory |
| 35 from slave import slave_utils | 35 from slave import slave_utils |
| 36 | 36 |
| 37 # Directory name, above the build directory, in which test results can be | 37 # Directory name, above the build directory, in which test results can be |
| 38 # found if no --results-dir option is given. | 38 # found if no --results-dir option is given. |
| 39 RESULT_DIR = 'layout-test-results' | 39 RESULT_DIR = 'layout-test-results' |
| 40 | 40 |
| 41 | 41 |
| 42 def _CollectArchiveFiles(output_dir): | 42 def _CollectArchiveFiles(output_dir): |
| 43 """Returns a pair of lists of file paths to archive. | 43 """Returns a list of actual layout test result files to archive.""" |
| 44 | |
| 45 The first list is all the actual results from the test run; | |
| 46 the second list is the diffs from the expected results. | |
| 47 | |
| 48 Files in |output_dir| or one of its subdirectories whose names end with | |
| 49 '-actual.txt' but not '-simp-actual.txt' or '-jsfilt-actual.txt' will | |
| 50 be included in the list. | |
| 51 """ | |
| 52 actual_file_list = [] | 44 actual_file_list = [] |
| 53 diff_file_list = [] | |
| 54 | 45 |
| 55 for path, _, files in os.walk(output_dir): | 46 for path, _, files in os.walk(output_dir): |
| 56 rel_path = path[len(output_dir + '\\'):] | 47 rel_path = path[len(output_dir + '\\'):] |
| 57 for name in files: | 48 for name in files: |
| 58 if _IsActualResultFile(name): | 49 if _IsActualResultFile(name): |
| 59 actual_file_list.append(os.path.join(rel_path, name)) | 50 actual_file_list.append(os.path.join(rel_path, name)) |
| 60 if _IsDiffFile(name): | |
| 61 diff_file_list.append(os.path.join(rel_path, name)) | |
| 62 elif name.endswith('.json'): | 51 elif name.endswith('.json'): |
| 63 actual_file_list.append(os.path.join(rel_path, name)) | 52 actual_file_list.append(os.path.join(rel_path, name)) |
| 64 | 53 |
| 65 if os.path.exists(os.path.join(output_dir, 'results.html')): | 54 if os.path.exists(os.path.join(output_dir, 'results.html')): |
| 66 actual_file_list.append('results.html') | 55 actual_file_list.append('results.html') |
| 67 | 56 |
| 68 if sys.platform == 'win32': | 57 if sys.platform == 'win32': |
| 69 if os.path.exists(os.path.join(output_dir, 'access_log.txt')): | 58 if os.path.exists(os.path.join(output_dir, 'access_log.txt')): |
| 70 actual_file_list.append('access_log.txt') | 59 actual_file_list.append('access_log.txt') |
| 71 if os.path.exists(os.path.join(output_dir, 'error_log.txt')): | 60 if os.path.exists(os.path.join(output_dir, 'error_log.txt')): |
| 72 actual_file_list.append('error_log.txt') | 61 actual_file_list.append('error_log.txt') |
| 73 | 62 |
| 74 return (actual_file_list, diff_file_list) | 63 return actual_file_list |
| 75 | 64 |
| 76 | 65 |
| 77 def _IsActualResultFile(name): | 66 def _IsActualResultFile(name): |
| 78 return ('-stack.' in name or | 67 return ('-stack.' in name or |
| 79 '-crash-log.' in name or | 68 '-crash-log.' in name or |
| 80 ('-actual.' in name and | 69 ('-actual.' in name and |
| 81 (os.path.splitext(name)[1] in | 70 (os.path.splitext(name)[1] in |
| 82 ('.txt', '.png', '.checksum', '.wav')) and | 71 ('.txt', '.png', '.checksum', '.wav')) and |
| 83 '-simp-actual.' not in name and | 72 '-simp-actual.' not in name and |
| 84 '-jsfilt-actual.' not in name)) | 73 '-jsfilt-actual.' not in name)) |
| 85 | 74 |
| 86 | 75 |
| 87 def _IsDiffFile(name): | |
| 88 return ('-wdiff.' in name or | |
| 89 '-expected.' in name or | |
| 90 name.endswith('-diff.txt') or | |
| 91 name.endswith('-diff.png')) | |
|
Dirk Pranke
2016/10/13 23:16:15
Why are you deleting these? We need these files.
qyearsley
2016/10/13 23:26:42
diff_file_list was only used in _ArchiveFullLayout
| |
| 92 | |
| 93 | |
| 94 def _ArchiveFullLayoutTestResults(staging_dir, dest_dir, diff_file_list, | |
| 95 options): | |
| 96 # Copy the actual and diff files to the web server. | |
| 97 # Don't clobber the staging_dir in the MakeZip call so that it keeps the | |
| 98 # files from the previous MakeZip call on diff_file_list. | |
| 99 print "archiving results + diffs" | |
| 100 full_zip_file = chromium_utils.MakeZip(staging_dir, | |
| 101 'layout-test-results', diff_file_list, options.results_dir, | |
| 102 remove_archive_directory=False)[1] | |
| 103 slave_utils.CopyFileToArchiveHost(full_zip_file, dest_dir) | |
| 104 | |
| 105 # Extract the files on the web server. | |
| 106 extract_dir = os.path.join(dest_dir, 'results') | |
| 107 print 'extracting zip file to %s' % extract_dir | |
| 108 | |
| 109 if chromium_utils.IsWindows(): | |
| 110 chromium_utils.ExtractZip(full_zip_file, extract_dir) | |
| 111 elif chromium_utils.IsLinux() or chromium_utils.IsMac(): | |
| 112 remote_zip_file = os.path.join(dest_dir, os.path.basename(full_zip_file)) | |
| 113 chromium_utils.SshExtractZip(archive_utils.Config.archive_host, | |
| 114 remote_zip_file, extract_dir) | |
| 115 | |
| 116 | |
| 117 def _CopyFileToArchiveHost(src, dest_dir): | |
| 118 """A wrapper method to copy files to the archive host. | |
| 119 | |
| 120 It calls CopyFileToDir on Windows and SshCopyFiles on Linux/Mac. | |
| 121 | |
| 122 TODO: we will eventually want to change the code to upload the | |
| 123 data to appengine. | |
| 124 | |
| 125 Args: | |
| 126 src: full path to the src file. | |
| 127 dest_dir: destination directory on the host. | |
| 128 """ | |
| 129 host = archive_utils.Config.archive_host | |
| 130 if not os.path.exists(src): | |
| 131 raise chromium_utils.ExternalError('Source path "%s" does not exist' % src) | |
| 132 chromium_utils.MakeWorldReadable(src) | |
| 133 if chromium_utils.IsWindows(): | |
| 134 chromium_utils.CopyFileToDir(src, dest_dir) | |
| 135 elif chromium_utils.IsLinux() or chromium_utils.IsMac(): | |
| 136 chromium_utils.SshCopyFiles(src, host, dest_dir) | |
| 137 else: | |
| 138 raise NotImplementedError( | |
| 139 'Platform "%s" is not currently supported.' % sys.platform) | |
| 140 | |
| 141 | |
| 142 def _MaybeMakeDirectoryOnArchiveHost(dest_dir): | |
| 143 """A wrapper method to create a directory on the archive host. | |
| 144 | |
| 145 It calls MaybeMakeDirectory on Windows and SshMakeDirectory on Linux/Mac. | |
| 146 | |
| 147 Args: | |
| 148 dest_dir: destination directory on the host. | |
| 149 """ | |
| 150 host = archive_utils.Config.archive_host | |
| 151 if chromium_utils.IsWindows(): | |
| 152 chromium_utils.MaybeMakeDirectory(dest_dir) | |
| 153 print 'saving results to %s' % dest_dir | |
| 154 elif chromium_utils.IsLinux() or chromium_utils.IsMac(): | |
| 155 chromium_utils.SshMakeDirectory(host, dest_dir) | |
| 156 print 'saving results to "%s" on "%s"' % (dest_dir, host) | |
| 157 else: | |
| 158 raise NotImplementedError( | |
| 159 'Platform "%s" is not currently supported.' % sys.platform) | |
| 160 | |
| 161 | |
| 162 def archive_layout(options): | 76 def archive_layout(options): |
| 163 chrome_dir = os.path.abspath(options.build_dir) | 77 chrome_dir = os.path.abspath(options.build_dir) |
| 164 results_dir_basename = os.path.basename(options.results_dir) | 78 results_dir_basename = os.path.basename(options.results_dir) |
| 165 if options.results_dir is not None: | 79 if options.results_dir is not None: |
| 166 options.results_dir = os.path.abspath(os.path.join(options.build_dir, | 80 options.results_dir = os.path.abspath(os.path.join(options.build_dir, |
| 167 options.results_dir)) | 81 options.results_dir)) |
| 168 else: | 82 else: |
| 169 options.results_dir = chromium_utils.FindUpward(chrome_dir, RESULT_DIR) | 83 options.results_dir = chromium_utils.FindUpward(chrome_dir, RESULT_DIR) |
| 170 print 'Archiving results from %s' % options.results_dir | 84 print 'Archiving results from %s' % options.results_dir |
| 171 staging_dir = options.staging_dir or slave_utils.GetStagingDir(chrome_dir) | 85 staging_dir = options.staging_dir or slave_utils.GetStagingDir(chrome_dir) |
| 172 print 'Staging in %s' % staging_dir | 86 print 'Staging in %s' % staging_dir |
| 173 if not os.path.exists(staging_dir): | 87 if not os.path.exists(staging_dir): |
| 174 os.makedirs(staging_dir) | 88 os.makedirs(staging_dir) |
| 175 | 89 |
| 176 (actual_file_list, diff_file_list) = _CollectArchiveFiles(options.results_dir) | 90 actual_file_list = _CollectArchiveFiles(options.results_dir) |
| 177 zip_file = chromium_utils.MakeZip(staging_dir, | 91 zip_file = chromium_utils.MakeZip(staging_dir, |
| 178 results_dir_basename, | 92 results_dir_basename, |
| 179 actual_file_list, | 93 actual_file_list, |
| 180 options.results_dir)[1] | 94 options.results_dir)[1] |
| 181 # TODO(crbug.com/655202): Stop separately uploading failing_results.json. | 95 # TODO(crbug.com/655202): Stop separately uploading failing_results.json. |
| 182 full_results_json = os.path.join(options.results_dir, 'full_results.json') | 96 full_results_json = os.path.join(options.results_dir, 'full_results.json') |
| 183 failing_results_json = os.path.join(options.results_dir, | 97 failing_results_json = os.path.join(options.results_dir, |
| 184 'failing_results.json') | 98 'failing_results.json') |
| 185 | 99 |
| 186 # Extract the build name of this slave (e.g., 'chrome-release') from its | 100 # Extract the build name of this slave (e.g., 'chrome-release') from its |
| 187 # configuration file if not provided as a param. | 101 # configuration file if not provided as a param. |
| 188 build_name = options.builder_name or slave_utils.SlaveBuildName(chrome_dir) | 102 build_name = options.builder_name or slave_utils.SlaveBuildName(chrome_dir) |
| 189 build_name = re.sub('[ .()]', '_', build_name) | 103 build_name = re.sub('[ .()]', '_', build_name) |
| 190 | 104 |
| 191 wc_dir = os.path.dirname(chrome_dir) | 105 wc_dir = os.path.dirname(chrome_dir) |
| 192 last_change = slave_utils.GetHashOrRevision(wc_dir) | 106 last_change = slave_utils.GetHashOrRevision(wc_dir) |
| 193 | 107 |
| 194 # TODO(dpranke): Is it safe to assume build_number is not blank? Should we | 108 # TODO(dpranke): Is it safe to assume build_number is not blank? Should we |
| 195 # assert() this ? | 109 # assert() this ? |
| 196 build_number = str(options.build_number) | 110 build_number = str(options.build_number) |
| 197 print 'last change: %s' % last_change | 111 print 'last change: %s' % last_change |
| 198 print 'build name: %s' % build_name | 112 print 'build name: %s' % build_name |
| 199 print 'build number: %s' % build_number | 113 print 'build number: %s' % build_number |
| 200 print 'host name: %s' % socket.gethostname() | 114 print 'host name: %s' % socket.gethostname() |
| 201 | 115 |
| 202 if options.gs_bucket: | 116 # Create a file containing last_change revision. This file will be uploaded |
| 203 # Create a file containing last_change revision. This file will be uploaded | 117 # after all layout test results are uploaded so the client can check this |
| 204 # after all layout test results are uploaded so the client can check this | 118 # file to see if the upload for the revision is complete. |
| 205 # file to see if the upload for the revision is complete. | 119 # See crbug.com/574272 for more details. |
| 206 # See crbug.com/574272 for more details. | 120 last_change_file = os.path.join(staging_dir, 'LAST_CHANGE') |
| 207 last_change_file = os.path.join(staging_dir, 'LAST_CHANGE') | 121 with open(last_change_file, 'w') as f: |
| 208 with open(last_change_file, 'w') as f: | 122 f.write(last_change) |
| 209 f.write(last_change) | |
| 210 | 123 |
| 211 # Copy the results to a directory archived by build number. | 124 # Copy the results to a directory archived by build number. |
| 212 gs_base = '/'.join([options.gs_bucket, build_name, build_number]) | 125 gs_base = '/'.join([options.gs_bucket, build_name, build_number]) |
| 213 gs_acl = options.gs_acl | 126 gs_acl = options.gs_acl |
| 214 # These files never change, cache for a year. | 127 # These files never change, cache for a year. |
| 215 cache_control = "public, max-age=31556926" | 128 cache_control = "public, max-age=31556926" |
| 216 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, | 129 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, |
| 217 cache_control=cache_control) | 130 cache_control=cache_control) |
| 218 slave_utils.GSUtilCopyDir(options.results_dir, gs_base, gs_acl=gs_acl, | 131 slave_utils.GSUtilCopyDir(options.results_dir, gs_base, gs_acl=gs_acl, |
| 219 cache_control=cache_control) | 132 cache_control=cache_control) |
| 220 | 133 |
| 221 # TODO(dpranke): Remove these two lines once clients are fetching the | 134 # TODO(dpranke): Remove these two lines once clients are fetching the |
| 222 # files from the layout-test-results dir. | 135 # files from the layout-test-results dir. |
| 223 slave_utils.GSUtilCopyFile(full_results_json, gs_base, gs_acl=gs_acl, | 136 slave_utils.GSUtilCopyFile(full_results_json, gs_base, gs_acl=gs_acl, |
| 224 cache_control=cache_control) | 137 cache_control=cache_control) |
| 225 slave_utils.GSUtilCopyFile(failing_results_json, gs_base, gs_acl=gs_acl, | 138 slave_utils.GSUtilCopyFile(failing_results_json, gs_base, gs_acl=gs_acl, |
| 226 cache_control=cache_control) | 139 cache_control=cache_control) |
| 227 | 140 |
| 228 slave_utils.GSUtilCopyFile(last_change_file, | 141 slave_utils.GSUtilCopyFile(last_change_file, |
| 229 gs_base + '/' + results_dir_basename, gs_acl=gs_acl, | 142 gs_base + '/' + results_dir_basename, |
| 230 cache_control=cache_control) | 143 gs_acl=gs_acl, |
| 144 cache_control=cache_control) | |
| 231 | 145 |
| 232 # And also to the 'results' directory to provide the 'latest' results | 146 # And also to the 'results' directory to provide the 'latest' results |
| 233 # and make sure they are not cached at all (Cloud Storage defaults to | 147 # and make sure they are not cached at all (Cloud Storage defaults to |
| 234 # caching w/ a max-age=3600). | 148 # caching w/ a max-age=3600). |
| 235 gs_base = '/'.join([options.gs_bucket, build_name, 'results']) | 149 gs_base = '/'.join([options.gs_bucket, build_name, 'results']) |
| 236 cache_control = 'no-cache' | 150 cache_control = 'no-cache' |
| 237 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, | 151 slave_utils.GSUtilCopyFile(zip_file, gs_base, gs_acl=gs_acl, |
| 238 cache_control=cache_control) | 152 cache_control=cache_control) |
| 239 slave_utils.GSUtilCopyDir(options.results_dir, gs_base, gs_acl=gs_acl, | 153 slave_utils.GSUtilCopyDir(options.results_dir, gs_base, gs_acl=gs_acl, |
| 240 cache_control=cache_control) | 154 cache_control=cache_control) |
| 241 | 155 slave_utils.GSUtilCopyFile(last_change_file, |
| 242 slave_utils.GSUtilCopyFile(last_change_file, | 156 gs_base + '/' + results_dir_basename, |
| 243 gs_base + '/' + results_dir_basename, gs_acl=gs_acl, | 157 gs_acl=gs_acl, |
| 244 cache_control=cache_control) | 158 cache_control=cache_control) |
| 245 | |
| 246 else: | |
| 247 # Where to save layout test results. | |
| 248 dest_parent_dir = os.path.join(archive_utils.Config.www_dir_base, | |
| 249 results_dir_basename.replace('-', '_'), build_name) | |
| 250 dest_dir = os.path.join(dest_parent_dir, last_change) | |
| 251 | |
| 252 _MaybeMakeDirectoryOnArchiveHost(dest_dir) | |
| 253 _CopyFileToArchiveHost(zip_file, dest_dir) | |
| 254 _CopyFileToArchiveHost(full_results_json, dest_dir) | |
| 255 _CopyFileToArchiveHost(failing_results_json, dest_dir) | |
| 256 # Not supported on Google Storage yet. | |
| 257 _ArchiveFullLayoutTestResults(staging_dir, dest_parent_dir, diff_file_list, | |
| 258 options) | |
| 259 return 0 | 159 return 0 |
| 260 | 160 |
| 261 | 161 |
| 262 def _ParseOptions(): | 162 def _ParseOptions(): |
| 263 option_parser = optparse.OptionParser() | 163 option_parser = optparse.OptionParser() |
| 264 option_parser.add_option('', '--build-dir', help='ignored') | 164 option_parser.add_option('', '--build-dir', help='ignored') |
| 265 option_parser.add_option('', '--results-dir', | 165 option_parser.add_option('', '--results-dir', |
| 266 help='path to layout test results, relative to ' | 166 help='path to layout test results, relative to ' |
| 267 'the build_dir') | 167 'the build_dir') |
| 268 option_parser.add_option('', '--builder-name', | 168 option_parser.add_option('', '--builder-name', |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 279 'instead of the master.')) | 179 'instead of the master.')) |
| 280 option_parser.add_option('', '--gs-acl', | 180 option_parser.add_option('', '--gs-acl', |
| 281 default=None, | 181 default=None, |
| 282 help=('The ACL of the google storage files.')) | 182 help=('The ACL of the google storage files.')) |
| 283 option_parser.add_option('--staging-dir', | 183 option_parser.add_option('--staging-dir', |
| 284 help='Directory to use for staging the archives. ' | 184 help='Directory to use for staging the archives. ' |
| 285 'Default behavior is to automatically detect ' | 185 'Default behavior is to automatically detect ' |
| 286 'slave\'s build directory.') | 186 'slave\'s build directory.') |
| 287 chromium_utils.AddPropertiesOptions(option_parser) | 187 chromium_utils.AddPropertiesOptions(option_parser) |
| 288 options, _ = option_parser.parse_args() | 188 options, _ = option_parser.parse_args() |
| 189 if not options.gs_bucket: | |
| 190 option_parser.error('--gs-bucket is required.') | |
| 289 options.build_dir = build_directory.GetBuildOutputDirectory() | 191 options.build_dir = build_directory.GetBuildOutputDirectory() |
| 290 return options | 192 return options |
| 291 | 193 |
| 292 | 194 |
| 293 def main(): | 195 def main(): |
| 294 options = _ParseOptions() | 196 options = _ParseOptions() |
| 295 logging.basicConfig(level=logging.INFO, | 197 logging.basicConfig(level=logging.INFO, |
| 296 format='%(asctime)s %(filename)s:%(lineno)-3d' | 198 format='%(asctime)s %(filename)s:%(lineno)-3d' |
| 297 ' %(levelname)s %(message)s', | 199 ' %(levelname)s %(message)s', |
| 298 datefmt='%y%m%d %H:%M:%S') | 200 datefmt='%y%m%d %H:%M:%S') |
| 299 return archive_layout(options) | 201 return archive_layout(options) |
| 300 | 202 |
| 301 | 203 |
| 302 if '__main__' == __name__: | 204 if '__main__' == __name__: |
| 303 sys.exit(main()) | 205 sys.exit(main()) |
| OLD | NEW |