| 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 |
| 11 import sys | 11 import sys |
| 12 import tempfile | 12 import tempfile |
| 13 import time | 13 import time |
| 14 import urllib2 | 14 import urllib2 |
| 15 import zipfile | 15 import zipfile |
| 16 | 16 |
| 17 from telemetry.page import profile_creator | 17 from telemetry.page import profile_creator |
| 18 | 18 |
| 19 import page_sets | 19 import page_sets |
| 20 | 20 |
| 21 from telemetry import benchmark |
| 22 from telemetry.page import page_test |
| 23 from telemetry.page import test_expectations |
| 24 from telemetry.results import results_options |
| 25 from telemetry.user_story import user_story_runner |
| 26 |
| 27 class _ExtensionPageTest(page_test.PageTest): |
| 28 """This page test verified that extensions were automatically installed.""" |
| 29 def __init__(self): |
| 30 super(_ExtensionPageTest, self).__init__() |
| 31 self._page_set = page_sets.Typical25PageSet() |
| 32 |
| 33 # Have the extensions been installed yet? |
| 34 self._extensions_installed = False |
| 35 |
| 36 # Expected |
| 37 self._expected_extension_count = 0 |
| 38 |
| 39 def CanRunForPage(self, page): |
| 40 # Superclass override. |
| 41 # No matter how many pages in the pageset, just perform two test |
| 42 # iterations. |
| 43 return page.page_set.pages.index(page) < 2 |
| 44 |
| 45 def ValidateAndMeasurePage(self, _, tab, results): |
| 46 # Superclass override. |
| 47 # Profile setup works in 2 phases: |
| 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 |
| 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 - |
| 52 # we are left with one open tab, wait for that to finish loading. |
| 53 |
| 54 # Sleep for a bit to allow safe browsing and other data to load + |
| 55 # extensions to install. |
| 56 if not self._extensions_installed: |
| 57 sleep_seconds = 5 * 60 |
| 58 logging.info("Sleeping for %d seconds." % sleep_seconds) |
| 59 time.sleep(sleep_seconds) |
| 60 self._extensions_installed = True |
| 61 else: |
| 62 # Phase 2: Wait for tab to finish loading. |
| 63 for i in xrange(len(tab.browser.tabs)): |
| 64 t = tab.browser.tabs[i] |
| 65 t.WaitForDocumentReadyStateToBeComplete() |
| 66 |
| 67 def DidRunTest(self, browser, results): |
| 68 """Superclass override.""" |
| 69 super(_ExtensionPageTest, self).DidRunTest(browser, |
| 70 results) |
| 71 # Do some basic sanity checks to make sure the profile is complete. |
| 72 installed_extensions = browser.extensions.keys() |
| 73 if not len(installed_extensions) == self._expected_extension_count: |
| 74 # Diagnosing errors: |
| 75 # Too many extensions: Managed environment may be installing additional |
| 76 # extensions. |
| 77 raise Exception("Unexpected number of extensions installed in browser", |
| 78 installed_extensions) |
| 79 |
| 21 | 80 |
| 22 def _ExternalExtensionsPath(): | 81 def _ExternalExtensionsPath(): |
| 23 """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 |
| 24 files""" | 83 files""" |
| 25 if platform.system() == 'Darwin': | 84 if platform.system() == 'Darwin': |
| 26 return os.path.join('/Library', 'Application Support', 'Google', 'Chrome', | 85 return os.path.join('/Library', 'Application Support', 'Google', 'Chrome', |
| 27 'External Extensions') | 86 'External Extensions') |
| 28 elif platform.system() == 'Linux': | 87 elif platform.system() == 'Linux': |
| 29 return os.path.join('/opt', 'google', 'chrome', 'extensions' ) | 88 return os.path.join('/opt', 'google', 'chrome', 'extensions' ) |
| 30 else: | 89 else: |
| 31 raise NotImplementedError('Extension install on %s is not yet supported' % | 90 raise NotImplementedError('Extension install on %s is not yet supported' % |
| 32 platform.system()) | 91 platform.system()) |
| 33 | 92 |
| 93 |
| 34 def _DownloadExtension(extension_id, output_dir): | 94 def _DownloadExtension(extension_id, output_dir): |
| 35 """Download an extension to disk. | 95 """Download an extension to disk. |
| 36 | 96 |
| 37 Args: | 97 Args: |
| 38 extension_id: the extension id. | 98 extension_id: the extension id. |
| 39 output_dir: Directory to download into. | 99 output_dir: Directory to download into. |
| 40 | 100 |
| 41 Returns: | 101 Returns: |
| 42 Extension file downloaded.""" | 102 Extension file downloaded.""" |
| 43 extension_download_path = os.path.join(output_dir, "%s.crx" % extension_id) | 103 extension_download_path = os.path.join(output_dir, "%s.crx" % extension_id) |
| 44 extension_url = ( | 104 extension_url = ( |
| 45 "https://clients2.google.com/service/update2/crx?response=redirect" | 105 "https://clients2.google.com/service/update2/crx?response=redirect" |
| 46 "&x=id%%3D%s%%26lang%%3Den-US%%26uc" % extension_id) | 106 "&x=id%%3D%s%%26lang%%3Den-US%%26uc" % extension_id) |
| 47 response = urllib2.urlopen(extension_url) | 107 response = urllib2.urlopen(extension_url) |
| 48 assert(response.getcode() == 200) | 108 assert(response.getcode() == 200) |
| 49 | 109 |
| 50 with open(extension_download_path, "w") as f: | 110 with open(extension_download_path, "w") as f: |
| 51 f.write(response.read()) | 111 f.write(response.read()) |
| 52 | 112 |
| 53 return extension_download_path | 113 return extension_download_path |
| 54 | 114 |
| 115 |
| 55 def _GetExtensionInfoFromCRX(crx_path): | 116 def _GetExtensionInfoFromCRX(crx_path): |
| 56 """Parse an extension archive and return information. | 117 """Parse an extension archive and return information. |
| 57 | 118 |
| 58 Note: | 119 Note: |
| 59 The extension name returned by this function may not be valid | 120 The extension name returned by this function may not be valid |
| 60 (e.g. in the case of a localized extension name). It's use is just | 121 (e.g. in the case of a localized extension name). It's use is just |
| 61 meant to be informational. | 122 meant to be informational. |
| 62 | 123 |
| 63 Args: | 124 Args: |
| 64 crx_path: path to crx archive to look at. | 125 crx_path: path to crx archive to look at. |
| 65 | 126 |
| 66 Returns: | 127 Returns: |
| 67 Tuple consisting of: | 128 Tuple consisting of: |
| 68 (crx_version, extension_name)""" | 129 (crx_version, extension_name)""" |
| 69 crx_zip = zipfile.ZipFile(crx_path) | 130 crx_zip = zipfile.ZipFile(crx_path) |
| 70 manifest_contents = crx_zip.read('manifest.json') | 131 manifest_contents = crx_zip.read('manifest.json') |
| 71 decoded_manifest = json.loads(manifest_contents) | 132 decoded_manifest = json.loads(manifest_contents) |
| 72 crx_version = decoded_manifest['version'] | 133 crx_version = decoded_manifest['version'] |
| 73 extension_name = decoded_manifest['name'] | 134 extension_name = decoded_manifest['name'] |
| 74 | 135 |
| 75 return (crx_version, extension_name) | 136 return (crx_version, extension_name) |
| 76 | 137 |
| 138 |
| 77 class ExtensionsProfileCreator(profile_creator.ProfileCreator): | 139 class ExtensionsProfileCreator(profile_creator.ProfileCreator): |
| 78 """Virtual base class for profile creators that install extensions. | 140 """Abstract base class for profile creators that install extensions. |
| 79 | 141 |
| 80 Extensions are installed using the mechanism described in | 142 Extensions are installed using the mechanism described in |
| 81 https://developer.chrome.com/extensions/external_extensions.html . | 143 https://developer.chrome.com/extensions/external_extensions.html . |
| 82 | 144 |
| 83 Subclasses are meant to be run interactively. | 145 Subclasses are meant to be run interactively. |
| 84 """ | 146 """ |
| 85 | |
| 86 def __init__(self, extensions_to_install=None, theme_to_install=None): | 147 def __init__(self, extensions_to_install=None, theme_to_install=None): |
| 87 self._CheckTestEnvironment() | 148 self._CheckTestEnvironment() |
| 88 | |
| 89 super(ExtensionsProfileCreator, self).__init__() | 149 super(ExtensionsProfileCreator, self).__init__() |
| 90 self._page_set = page_sets.Typical25() | |
| 91 | |
| 92 # Directory into which the output profile is written. | |
| 93 self._output_profile_path = None | |
| 94 | 150 |
| 95 # List of extensions to install. | 151 # List of extensions to install. |
| 96 self._extensions_to_install = list(extensions_to_install or []) | 152 self._extensions_to_install = list(extensions_to_install or []) |
| 97 | 153 |
| 98 # Theme to install (if any). | 154 # Theme to install (if any). |
| 99 self._theme_to_install = theme_to_install | 155 self._theme_to_install = theme_to_install |
| 100 | 156 |
| 101 # Directory to download extension files into. | 157 # Directory to download extension files into. |
| 102 self._extension_download_dir = None | 158 self._extension_download_dir = None |
| 103 | 159 |
| 104 # Have the extensions been installed yet? | |
| 105 self._extensions_installed = False | |
| 106 | |
| 107 # List of files to delete after run. | 160 # List of files to delete after run. |
| 108 self._files_to_cleanup = [] | 161 self._files_to_cleanup = [] |
| 109 | 162 |
| 110 self._PrepareExtensionInstallFiles() | |
| 111 | |
| 112 def _CheckTestEnvironment(self): | 163 def _CheckTestEnvironment(self): |
| 113 # Running this script on a corporate network or other managed environment | 164 # Running this script on a corporate network or other managed environment |
| 114 # could potentially alter the profile contents. | 165 # could potentially alter the profile contents. |
| 115 hostname = socket.gethostname() | 166 hostname = socket.gethostname() |
| 116 if hostname.endswith('corp.google.com'): | 167 if hostname.endswith('corp.google.com'): |
| 117 raise Exception("It appears you are connected to a corporate network " | 168 raise Exception("It appears you are connected to a corporate network " |
| 118 "(hostname=%s). This script needs to be run off the corp " | 169 "(hostname=%s). This script needs to be run off the corp " |
| 119 "network." % hostname) | 170 "network." % hostname) |
| 120 | 171 |
| 121 prompt = ("\n!!!This script must be run on a fresh OS installation, " | 172 prompt = ("\n!!!This script must be run on a fresh OS installation, " |
| 122 "disconnected from any corporate network. Are you sure you want to " | 173 "disconnected from any corporate network. Are you sure you want to " |
| 123 "continue? (y/N) ") | 174 "continue? (y/N) ") |
| 124 if (raw_input(prompt).lower() != 'y'): | 175 if (raw_input(prompt).lower() != 'y'): |
| 125 sys.exit(-1) | 176 sys.exit(-1) |
| 126 | 177 |
| 178 def Run(self, options): |
| 179 self._PrepareExtensionInstallFiles() |
| 180 |
| 181 expectations = test_expectations.TestExpectations() |
| 182 results = results_options.CreateResults( |
| 183 benchmark.BenchmarkMetadata(profile_creator.__class__.__name__), |
| 184 options) |
| 185 extension_page_test = _ExtensionPageTest() |
| 186 extension_page_test._expected_extension_count = len( |
| 187 self._extensions_to_install) |
| 188 user_story_runner.Run(extension_page_test, extension_page_test._page_set, |
| 189 expectations, options, results) |
| 190 |
| 191 self._CleanupExtensionInstallFiles() |
| 192 |
| 193 # Check that files on this list exist and have content. |
| 194 expected_files = [ |
| 195 os.path.join('Default', 'Network Action Predictor')] |
| 196 for filename in expected_files: |
| 197 filename = os.path.join(options.output_profile_path, filename) |
| 198 if not os.path.getsize(filename) > 0: |
| 199 raise Exception("Profile not complete: %s is zero length." % filename) |
| 200 |
| 201 if results.failures: |
| 202 logging.warning('Some pages failed.') |
| 203 logging.warning('Failed pages:\n%s', |
| 204 '\n'.join(map(str, results.pages_that_failed))) |
| 205 raise Exception('ExtensionsProfileCreator failed.') |
| 206 |
| 127 def _PrepareExtensionInstallFiles(self): | 207 def _PrepareExtensionInstallFiles(self): |
| 128 """Download extension archives and create extension install files.""" | 208 """Download extension archives and create extension install files.""" |
| 129 extensions_to_install = self._extensions_to_install | 209 extensions_to_install = self._extensions_to_install |
| 130 if self._theme_to_install: | 210 if self._theme_to_install: |
| 131 extensions_to_install.append(self._theme_to_install) | 211 extensions_to_install.append(self._theme_to_install) |
| 132 if not extensions_to_install: | 212 if not extensions_to_install: |
| 133 raise ValueError("No extensions or themes to install:", | 213 raise ValueError("No extensions or themes to install:", |
| 134 extensions_to_install) | 214 extensions_to_install) |
| 135 | 215 |
| 136 # Create external extensions path if it doesn't exist already. | 216 # Create external extensions path if it doesn't exist already. |
| (...skipping 24 matching lines...) Expand all Loading... |
| 161 logging.info("Cleaning up stray files") | 241 logging.info("Cleaning up stray files") |
| 162 for filename in self._files_to_cleanup: | 242 for filename in self._files_to_cleanup: |
| 163 os.remove(filename) | 243 os.remove(filename) |
| 164 | 244 |
| 165 if self._extension_download_dir: | 245 if self._extension_download_dir: |
| 166 # Simple sanity check to lessen the impact of a stray rmtree(). | 246 # Simple sanity check to lessen the impact of a stray rmtree(). |
| 167 if len(self._extension_download_dir.split(os.sep)) < 3: | 247 if len(self._extension_download_dir.split(os.sep)) < 3: |
| 168 raise Exception("Path too shallow: %s" % self._extension_download_dir) | 248 raise Exception("Path too shallow: %s" % self._extension_download_dir) |
| 169 shutil.rmtree(self._extension_download_dir) | 249 shutil.rmtree(self._extension_download_dir) |
| 170 self._extension_download_dir = None | 250 self._extension_download_dir = None |
| 171 | |
| 172 def CustomizeBrowserOptions(self, options): | |
| 173 self._output_profile_path = options.output_profile_path | |
| 174 | |
| 175 def DidRunTest(self, browser, results): | |
| 176 """Run before exit.""" | |
| 177 super(ExtensionsProfileCreator, self).DidRunTest() | |
| 178 # Do some basic sanity checks to make sure the profile is complete. | |
| 179 installed_extensions = browser.extensions.keys() | |
| 180 if not len(installed_extensions) == len(self._extensions_to_install): | |
| 181 # Diagnosing errors: | |
| 182 # Too many extensions: Managed environment may be installing additional | |
| 183 # extensions. | |
| 184 raise Exception("Unexpected number of extensions installed in browser", | |
| 185 installed_extensions) | |
| 186 | |
| 187 # Check that files on this list exist and have content. | |
| 188 expected_files = [ | |
| 189 os.path.join('Default', 'Network Action Predictor')] | |
| 190 for filename in expected_files: | |
| 191 filename = os.path.join(self._output_profile_path, filename) | |
| 192 if not os.path.getsize(filename) > 0: | |
| 193 raise Exception("Profile not complete: %s is zero length." % filename) | |
| 194 | |
| 195 self._CleanupExtensionInstallFiles() | |
| 196 | |
| 197 def CanRunForPage(self, page): | |
| 198 # No matter how many pages in the pageset, just perform two test iterations. | |
| 199 return page.page_set.pages.index(page) < 2 | |
| 200 | |
| 201 def ValidateAndMeasurePage(self, _, tab, results): | |
| 202 # Profile setup works in 2 phases: | |
| 203 # Phase 1: When the first page is loaded: we wait for a timeout to allow | |
| 204 # all extensions to install and to prime safe browsing and other | |
| 205 # caches. Extensions may open tabs as part of the install process. | |
| 206 # Phase 2: When the second page loads, user_story_runner closes all tabs - | |
| 207 # we are left with one open tab, wait for that to finish loading. | |
| 208 | |
| 209 # Sleep for a bit to allow safe browsing and other data to load + | |
| 210 # extensions to install. | |
| 211 if not self._extensions_installed: | |
| 212 sleep_seconds = 5 * 60 | |
| 213 logging.info("Sleeping for %d seconds." % sleep_seconds) | |
| 214 time.sleep(sleep_seconds) | |
| 215 self._extensions_installed = True | |
| 216 else: | |
| 217 # Phase 2: Wait for tab to finish loading. | |
| 218 for i in xrange(len(tab.browser.tabs)): | |
| 219 t = tab.browser.tabs[i] | |
| 220 t.WaitForDocumentReadyStateToBeComplete() | |
| OLD | NEW |