| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import json | 5 import json |
| 6 import logging | 6 import logging |
| 7 import os | 7 import os |
| 8 import platform | 8 import platform |
| 9 import shutil | 9 import shutil |
| 10 import socket | 10 import socket |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 47 # Profile setup works in 2 phases: | 47 # Profile setup works in 2 phases: |
| 48 # Phase 1: When the first page is loaded: we wait for a timeout to allow | 48 # Phase 1: When the first page is loaded: we wait for a timeout to allow |
| 49 # all extensions to install and to prime safe browsing and other | 49 # all extensions to install and to prime safe browsing and other |
| 50 # caches. Extensions may open tabs as part of the install process. | 50 # caches. Extensions may open tabs as part of the install process. |
| 51 # Phase 2: When the second page loads, user_story_runner closes all tabs - | 51 # Phase 2: When the second page loads, user_story_runner closes all tabs - |
| 52 # we are left with one open tab, wait for that to finish loading. | 52 # we are left with one open tab, wait for that to finish loading. |
| 53 | 53 |
| 54 # Sleep for a bit to allow safe browsing and other data to load + | 54 # Sleep for a bit to allow safe browsing and other data to load + |
| 55 # extensions to install. | 55 # extensions to install. |
| 56 if not self._extensions_installed: | 56 if not self._extensions_installed: |
| 57 sleep_seconds = 5 * 60 | 57 sleep_seconds = 15 |
| 58 logging.info("Sleeping for %d seconds." % sleep_seconds) | 58 logging.info("Sleeping for %d seconds." % sleep_seconds) |
| 59 time.sleep(sleep_seconds) | 59 time.sleep(sleep_seconds) |
| 60 self._extensions_installed = True | 60 self._extensions_installed = True |
| 61 else: | 61 else: |
| 62 # Phase 2: Wait for tab to finish loading. | 62 # Phase 2: Wait for tab to finish loading. |
| 63 for i in xrange(len(tab.browser.tabs)): | 63 for i in xrange(len(tab.browser.tabs)): |
| 64 t = tab.browser.tabs[i] | 64 t = tab.browser.tabs[i] |
| 65 t.WaitForDocumentReadyStateToBeComplete() | 65 t.WaitForDocumentReadyStateToBeComplete() |
| 66 | 66 |
| 67 def DidRunTest(self, browser, results): | 67 def DidRunTest(self, browser, results): |
| 68 """Superclass override.""" | 68 """Superclass override.""" |
| 69 super(_ExtensionPageTest, self).DidRunTest(browser, | 69 super(_ExtensionPageTest, self).DidRunTest(browser, |
| 70 results) | 70 results) |
| 71 # Do some basic sanity checks to make sure the profile is complete. | 71 # Do some basic sanity checks to make sure the profile is complete. |
| 72 installed_extensions = browser.extensions.keys() | 72 installed_extensions = browser.extensions.keys() |
| 73 if not len(installed_extensions) == self._expected_extension_count: | 73 if not len(installed_extensions) == self._expected_extension_count: |
| 74 # Diagnosing errors: | 74 # Diagnosing errors: |
| 75 # Too many extensions: Managed environment may be installing additional | 75 # Too many extensions: Managed environment may be installing additional |
| 76 # extensions. | 76 # extensions. |
| 77 raise Exception("Unexpected number of extensions installed in browser", | 77 raise Exception("Unexpected number of extensions installed in browser", |
| 78 installed_extensions) | 78 installed_extensions) |
| 79 | 79 |
| 80 | 80 |
| 81 def _ExternalExtensionsPath(): | 81 def _ExternalExtensionsPath(profile_path): |
| 82 """Returns the OS-dependent path at which to install the extension deployment | 82 """Returns the OS-dependent path at which to install the extension deployment |
| 83 files""" | 83 files. |
| 84 |
| 85 |profile_path| is the path of the profile that will be used to launch the |
| 86 browser. |
| 87 """ |
| 84 if platform.system() == 'Darwin': | 88 if platform.system() == 'Darwin': |
| 85 return os.path.join('/Library', 'Application Support', 'Google', 'Chrome', | 89 return str(profile_path) + '/External Extensions' |
| 86 'External Extensions') | 90 |
| 87 elif platform.system() == 'Linux': | |
| 88 return os.path.join('/opt', 'google', 'chrome', 'extensions' ) | |
| 89 else: | 91 else: |
| 90 raise NotImplementedError('Extension install on %s is not yet supported' % | 92 raise NotImplementedError('Extension install on %s is not yet supported' % |
| 91 platform.system()) | 93 platform.system()) |
| 92 | 94 |
| 93 | 95 |
| 94 def _DownloadExtension(extension_id, output_dir): | 96 def _DownloadExtension(extension_id, output_dir): |
| 95 """Download an extension to disk. | 97 """Download an extension to disk. |
| 96 | 98 |
| 97 Args: | 99 Args: |
| 98 extension_id: the extension id. | 100 extension_id: the extension id. |
| 99 output_dir: Directory to download into. | 101 output_dir: Directory to download into. |
| 100 | 102 |
| 101 Returns: | 103 Returns: |
| 102 Extension file downloaded.""" | 104 Extension file downloaded.""" |
| 103 extension_download_path = os.path.join(output_dir, "%s.crx" % extension_id) | 105 extension_download_path = os.path.join(output_dir, "%s.crx" % extension_id) |
| 106 |
| 107 # Ideally, the Chrome version would be dynamically extracted from the binary. |
| 108 # Instead, we use a Chrome version whose release date is expected to be |
| 109 # about a hundred years in the future. |
| 110 chrome_version = '1000.0.0.0' |
| 104 extension_url = ( | 111 extension_url = ( |
| 105 "https://clients2.google.com/service/update2/crx?response=redirect" | 112 "https://clients2.google.com/service/update2/crx?response=redirect" |
| 106 "&x=id%%3D%s%%26lang%%3Den-US%%26uc" % extension_id) | 113 "&prodversion=%s&x=id%%3D%s%%26lang%%3Den-US%%26uc" |
| 114 % (chrome_version, extension_id)) |
| 107 response = urllib2.urlopen(extension_url) | 115 response = urllib2.urlopen(extension_url) |
| 108 assert(response.getcode() == 200) | 116 assert(response.getcode() == 200) |
| 109 | 117 |
| 110 with open(extension_download_path, "w") as f: | 118 with open(extension_download_path, "w") as f: |
| 111 f.write(response.read()) | 119 f.write(response.read()) |
| 112 | 120 |
| 113 return extension_download_path | 121 return extension_download_path |
| 114 | 122 |
| 115 | 123 |
| 116 def _GetExtensionInfoFromCRX(crx_path): | 124 def _GetExtensionInfoFromCRX(crx_path): |
| (...skipping 21 matching lines...) Expand all Loading... |
| 138 | 146 |
| 139 class ExtensionsProfileCreator(profile_creator.ProfileCreator): | 147 class ExtensionsProfileCreator(profile_creator.ProfileCreator): |
| 140 """Abstract base class for profile creators that install extensions. | 148 """Abstract base class for profile creators that install extensions. |
| 141 | 149 |
| 142 Extensions are installed using the mechanism described in | 150 Extensions are installed using the mechanism described in |
| 143 https://developer.chrome.com/extensions/external_extensions.html . | 151 https://developer.chrome.com/extensions/external_extensions.html . |
| 144 | 152 |
| 145 Subclasses are meant to be run interactively. | 153 Subclasses are meant to be run interactively. |
| 146 """ | 154 """ |
| 147 def __init__(self, extensions_to_install=None, theme_to_install=None): | 155 def __init__(self, extensions_to_install=None, theme_to_install=None): |
| 148 self._CheckTestEnvironment() | |
| 149 super(ExtensionsProfileCreator, self).__init__() | 156 super(ExtensionsProfileCreator, self).__init__() |
| 150 | 157 |
| 151 # List of extensions to install. | 158 # List of extensions to install. |
| 152 self._extensions_to_install = list(extensions_to_install or []) | 159 self._extensions_to_install = [] |
| 153 | 160 if extensions_to_install: |
| 154 # Theme to install (if any). | 161 self._extensions_to_install.extend(extensions_to_install) |
| 155 self._theme_to_install = theme_to_install | 162 if theme_to_install: |
| 163 self._extensions_to_install.append(theme_to_install) |
| 156 | 164 |
| 157 # Directory to download extension files into. | 165 # Directory to download extension files into. |
| 158 self._extension_download_dir = None | 166 self._extension_download_dir = None |
| 159 | 167 |
| 160 # List of files to delete after run. | 168 # List of files to delete after run. |
| 161 self._files_to_cleanup = [] | 169 self._files_to_cleanup = [] |
| 162 | 170 |
| 163 def _CheckTestEnvironment(self): | 171 def Run(self, options): |
| 164 # Running this script on a corporate network or other managed environment | 172 # Installing extensions requires that the profile directory exist before |
| 165 # could potentially alter the profile contents. | 173 # the browser is launched. |
| 166 hostname = socket.gethostname() | 174 if not options.browser_options.profile_dir: |
| 167 if hostname.endswith('corp.google.com'): | 175 options.browser_options.profile_dir = tempfile.mkdtemp() |
| 168 raise Exception("It appears you are connected to a corporate network " | 176 options.browser_options.disable_default_apps = False |
| 169 "(hostname=%s). This script needs to be run off the corp " | |
| 170 "network." % hostname) | |
| 171 | 177 |
| 172 prompt = ("\n!!!This script must be run on a fresh OS installation, " | 178 self._PrepareExtensionInstallFiles(options.browser_options.profile_dir) |
| 173 "disconnected from any corporate network. Are you sure you want to " | |
| 174 "continue? (y/N) ") | |
| 175 if (raw_input(prompt).lower() != 'y'): | |
| 176 sys.exit(-1) | |
| 177 | |
| 178 def Run(self, options): | |
| 179 self._PrepareExtensionInstallFiles() | |
| 180 | 179 |
| 181 expectations = test_expectations.TestExpectations() | 180 expectations = test_expectations.TestExpectations() |
| 182 results = results_options.CreateResults( | 181 results = results_options.CreateResults( |
| 183 benchmark.BenchmarkMetadata(profile_creator.__class__.__name__), | 182 benchmark.BenchmarkMetadata(profile_creator.__class__.__name__), |
| 184 options) | 183 options) |
| 185 extension_page_test = _ExtensionPageTest() | 184 extension_page_test = _ExtensionPageTest() |
| 186 extension_page_test._expected_extension_count = len( | 185 extension_page_test._expected_extension_count = len( |
| 187 self._extensions_to_install) | 186 self._extensions_to_install) |
| 188 user_story_runner.Run(extension_page_test, extension_page_test._page_set, | 187 user_story_runner.Run(extension_page_test, extension_page_test._page_set, |
| 189 expectations, options, results) | 188 expectations, options, results) |
| 190 | 189 |
| 191 self._CleanupExtensionInstallFiles() | 190 self._CleanupExtensionInstallFiles() |
| 192 | 191 |
| 193 # Check that files on this list exist and have content. | 192 # Check that files on this list exist and have content. |
| 194 expected_files = [ | 193 expected_files = [ |
| 195 os.path.join('Default', 'Network Action Predictor')] | 194 os.path.join('Default', 'Network Action Predictor')] |
| 196 for filename in expected_files: | 195 for filename in expected_files: |
| 197 filename = os.path.join(options.output_profile_path, filename) | 196 filename = os.path.join(options.output_profile_path, filename) |
| 198 if not os.path.getsize(filename) > 0: | 197 if not os.path.getsize(filename) > 0: |
| 199 raise Exception("Profile not complete: %s is zero length." % filename) | 198 raise Exception("Profile not complete: %s is zero length." % filename) |
| 200 | 199 |
| 201 if results.failures: | 200 if results.failures: |
| 202 logging.warning('Some pages failed.') | 201 logging.warning('Some pages failed.') |
| 203 logging.warning('Failed pages:\n%s', | 202 logging.warning('Failed pages:\n%s', |
| 204 '\n'.join(map(str, results.pages_that_failed))) | 203 '\n'.join(map(str, results.pages_that_failed))) |
| 205 raise Exception('ExtensionsProfileCreator failed.') | 204 raise Exception('ExtensionsProfileCreator failed.') |
| 206 | 205 |
| 207 def _PrepareExtensionInstallFiles(self): | 206 def _PrepareExtensionInstallFiles(self, profile_path): |
| 208 """Download extension archives and create extension install files.""" | 207 """Download extension archives and create extension install files.""" |
| 209 extensions_to_install = self._extensions_to_install | 208 extensions_to_install = self._extensions_to_install |
| 210 if self._theme_to_install: | |
| 211 extensions_to_install.append(self._theme_to_install) | |
| 212 if not extensions_to_install: | 209 if not extensions_to_install: |
| 213 raise ValueError("No extensions or themes to install:", | 210 raise ValueError("No extensions or themes to install:", |
| 214 extensions_to_install) | 211 extensions_to_install) |
| 215 | 212 |
| 216 # Create external extensions path if it doesn't exist already. | 213 # Create external extensions path if it doesn't exist already. |
| 217 external_extensions_dir = _ExternalExtensionsPath() | 214 external_extensions_dir = _ExternalExtensionsPath(profile_path) |
| 218 if not os.path.isdir(external_extensions_dir): | 215 if not os.path.isdir(external_extensions_dir): |
| 219 os.makedirs(external_extensions_dir) | 216 os.makedirs(external_extensions_dir) |
| 220 | 217 |
| 221 self._extension_download_dir = tempfile.mkdtemp() | 218 self._extension_download_dir = tempfile.mkdtemp() |
| 222 | 219 |
| 223 num_extensions = len(extensions_to_install) | 220 num_extensions = len(extensions_to_install) |
| 224 for i, extension_id in extensions_to_install: | 221 for i in range(num_extensions): |
| 222 extension_id = extensions_to_install[i] |
| 225 logging.info("Downloading %s - %d/%d" % ( | 223 logging.info("Downloading %s - %d/%d" % ( |
| 226 extension_id, (i + 1), num_extensions)) | 224 extension_id, (i + 1), num_extensions)) |
| 227 extension_path = _DownloadExtension(extension_id, | 225 extension_path = _DownloadExtension(extension_id, |
| 228 self._extension_download_dir) | 226 self._extension_download_dir) |
| 229 (version, name) = _GetExtensionInfoFromCRX(extension_path) | 227 (version, name) = _GetExtensionInfoFromCRX(extension_path) |
| 230 extension_info = {'external_crx' : extension_path, | 228 extension_info = {'external_crx' : extension_path, |
| 231 'external_version' : version, | 229 'external_version' : version, |
| 232 '_comment' : name} | 230 '_comment' : name} |
| 233 extension_json_path = os.path.join(external_extensions_dir, | 231 extension_json_path = os.path.join(external_extensions_dir, |
| 234 "%s.json" % extension_id) | 232 "%s.json" % extension_id) |
| 235 with open(extension_json_path, 'w') as f: | 233 with open(extension_json_path, 'w') as f: |
| 236 f.write(json.dumps(extension_info)) | 234 f.write(json.dumps(extension_info)) |
| 237 self._files_to_cleanup.append(extension_json_path) | 235 self._files_to_cleanup.append(extension_json_path) |
| 238 | 236 |
| 239 def _CleanupExtensionInstallFiles(self): | 237 def _CleanupExtensionInstallFiles(self): |
| 240 """Cleanup stray files before exiting.""" | 238 """Cleanup stray files before exiting.""" |
| 241 logging.info("Cleaning up stray files") | 239 logging.info("Cleaning up stray files") |
| 242 for filename in self._files_to_cleanup: | 240 for filename in self._files_to_cleanup: |
| 243 os.remove(filename) | 241 os.remove(filename) |
| 244 | 242 |
| 245 if self._extension_download_dir: | 243 if self._extension_download_dir: |
| 246 # Simple sanity check to lessen the impact of a stray rmtree(). | 244 # Simple sanity check to lessen the impact of a stray rmtree(). |
| 247 if len(self._extension_download_dir.split(os.sep)) < 3: | 245 if len(self._extension_download_dir.split(os.sep)) < 3: |
| 248 raise Exception("Path too shallow: %s" % self._extension_download_dir) | 246 raise Exception("Path too shallow: %s" % self._extension_download_dir) |
| 249 shutil.rmtree(self._extension_download_dir) | 247 shutil.rmtree(self._extension_download_dir) |
| 250 self._extension_download_dir = None | 248 self._extension_download_dir = None |
| OLD | NEW |