Chromium Code Reviews| 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 | |
| 21 | 26 |
| 22 def _ExternalExtensionsPath(): | 27 def _ExternalExtensionsPath(): |
| 23 """Returns the OS-dependent path at which to install the extension deployment | 28 """Returns the OS-dependent path at which to install the extension deployment |
| 24 files""" | 29 files""" |
| 25 if platform.system() == 'Darwin': | 30 if platform.system() == 'Darwin': |
| 26 return os.path.join('/Library', 'Application Support', 'Google', 'Chrome', | 31 return os.path.join('/Library', 'Application Support', 'Google', 'Chrome', |
| 27 'External Extensions') | 32 'External Extensions') |
| 28 elif platform.system() == 'Linux': | 33 elif platform.system() == 'Linux': |
| 29 return os.path.join('/opt', 'google', 'chrome', 'extensions' ) | 34 return os.path.join('/opt', 'google', 'chrome', 'extensions' ) |
| 30 else: | 35 else: |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 68 (crx_version, extension_name)""" | 73 (crx_version, extension_name)""" |
| 69 crx_zip = zipfile.ZipFile(crx_path) | 74 crx_zip = zipfile.ZipFile(crx_path) |
| 70 manifest_contents = crx_zip.read('manifest.json') | 75 manifest_contents = crx_zip.read('manifest.json') |
| 71 decoded_manifest = json.loads(manifest_contents) | 76 decoded_manifest = json.loads(manifest_contents) |
| 72 crx_version = decoded_manifest['version'] | 77 crx_version = decoded_manifest['version'] |
| 73 extension_name = decoded_manifest['name'] | 78 extension_name = decoded_manifest['name'] |
| 74 | 79 |
| 75 return (crx_version, extension_name) | 80 return (crx_version, extension_name) |
| 76 | 81 |
| 77 class ExtensionsProfileCreator(profile_creator.ProfileCreator): | 82 class ExtensionsProfileCreator(profile_creator.ProfileCreator): |
| 78 """Virtual base class for profile creators that install extensions. | 83 """Abstract base class for profile creators that install extensions. |
| 79 | 84 |
| 80 Extensions are installed using the mechanism described in | 85 Extensions are installed using the mechanism described in |
| 81 https://developer.chrome.com/extensions/external_extensions.html . | 86 https://developer.chrome.com/extensions/external_extensions.html . |
| 82 | 87 |
| 83 Subclasses are meant to be run interactively. | 88 Subclasses are meant to be run interactively. |
| 84 """ | 89 """ |
| 90 class PageTest(page_test.PageTest): | |
|
dtu
2015/01/09 01:23:58
I'm finding the nested classes hard to read. Can y
erikchen
2015/01/09 03:12:42
Done. I renamed it to _ExtensionPageTest just to m
| |
| 91 def __init__(self): | |
| 92 super(ExtensionsProfileCreator.PageTest, self).__init__() | |
| 93 self._page_set = page_sets.Typical25PageSet() | |
| 94 | |
| 95 # Have the extensions been installed yet? | |
| 96 self._extensions_installed = False | |
| 97 | |
| 98 # Expected | |
| 99 self._expected_extension_count = 0 | |
| 100 | |
| 101 def CanRunForPage(self, page): | |
| 102 # Superclass override. | |
| 103 # No matter how many pages in the pageset, just perform two test | |
| 104 # iterations. | |
| 105 return page.page_set.pages.index(page) < 2 | |
| 106 | |
| 107 def ValidateAndMeasurePage(self, _, tab, results): | |
| 108 # Superclass override. | |
| 109 # Profile setup works in 2 phases: | |
| 110 # Phase 1: When the first page is loaded: we wait for a timeout to allow | |
| 111 # all extensions to install and to prime safe browsing and other | |
| 112 # caches. Extensions may open tabs as part of the install process. | |
| 113 # Phase 2: When the second page loads, user_story_runner closes all tabs - | |
| 114 # we are left with one open tab, wait for that to finish loading. | |
| 115 | |
| 116 # Sleep for a bit to allow safe browsing and other data to load + | |
| 117 # extensions to install. | |
| 118 if not self._extensions_installed: | |
| 119 sleep_seconds = 5 * 60 | |
| 120 logging.info("Sleeping for %d seconds." % sleep_seconds) | |
| 121 time.sleep(sleep_seconds) | |
| 122 self._extensions_installed = True | |
| 123 else: | |
| 124 # Phase 2: Wait for tab to finish loading. | |
| 125 for i in xrange(len(tab.browser.tabs)): | |
| 126 t = tab.browser.tabs[i] | |
| 127 t.WaitForDocumentReadyStateToBeComplete() | |
| 128 | |
| 129 def DidRunTest(self, browser, results): | |
| 130 """Superclass override.""" | |
| 131 super(ExtensionsProfileCreator.PageTest, self).DidRunTest(browser, | |
| 132 results) | |
| 133 # Do some basic sanity checks to make sure the profile is complete. | |
| 134 installed_extensions = browser.extensions.keys() | |
| 135 if not len(installed_extensions) == self._expected_extension_count: | |
| 136 # Diagnosing errors: | |
| 137 # Too many extensions: Managed environment may be installing additional | |
| 138 # extensions. | |
| 139 raise Exception("Unexpected number of extensions installed in browser", | |
| 140 installed_extensions) | |
| 141 | |
| 85 | 142 |
| 86 def __init__(self): | 143 def __init__(self): |
| 87 super(ExtensionsProfileCreator, self).__init__() | 144 super(ExtensionsProfileCreator, self).__init__() |
| 88 self._page_set = page_sets.Typical25() | 145 self._page_test = ExtensionsProfileCreator.PageTest() |
|
dtu
2015/01/09 01:23:58
Why create a side-effect on the class if you're us
erikchen
2015/01/09 03:12:42
I removed the member, and made it a local variable
| |
| 89 | |
| 90 # Directory into which the output profile is written. | |
| 91 self._output_profile_path = None | |
| 92 | 146 |
| 93 # List of extensions to install. | 147 # List of extensions to install. |
| 94 self._extensions_to_install = [] | 148 self._extensions_to_install = [] |
| 95 | 149 |
| 96 # Theme to install (if any). | 150 # Theme to install (if any). |
| 97 self._theme_to_install = None | 151 self._theme_to_install = None |
| 98 | 152 |
| 99 # Directory to download extension files into. | 153 # Directory to download extension files into. |
| 100 self._extension_download_dir = None | 154 self._extension_download_dir = None |
| 101 | 155 |
| 102 # Have the extensions been installed yet? | |
| 103 self._extensions_installed = False | |
| 104 | |
| 105 # List of files to delete after run. | 156 # List of files to delete after run. |
| 106 self._files_to_cleanup = [] | 157 self._files_to_cleanup = [] |
| 107 | 158 |
| 159 def Run(self, options): | |
| 160 self._PrepareExtensionInstallFiles() | |
| 161 | |
| 162 expectations = test_expectations.TestExpectations() | |
| 163 results = results_options.CreateResults( | |
| 164 benchmark.BenchmarkMetadata(profile_creator.__class__.__name__), | |
| 165 options) | |
| 166 self._page_test._expected_extension_count = len(self._extensions_to_install) | |
| 167 user_story_runner.Run(self._page_test, self._page_test._page_set, | |
| 168 expectations, options, results) | |
| 169 | |
| 170 self._CleanupExtensionInstallFiles() | |
| 171 | |
| 172 # Check that files on this list exist and have content. | |
| 173 expected_files = [ | |
| 174 os.path.join('Default', 'Network Action Predictor')] | |
| 175 for filename in expected_files: | |
| 176 filename = os.path.join(options.output_profile_path, filename) | |
| 177 if not os.path.getsize(filename) > 0: | |
| 178 raise Exception("Profile not complete: %s is zero length." % filename) | |
| 179 | |
| 180 if results.failures: | |
| 181 logging.warning('Some pages failed.') | |
| 182 logging.warning('Failed pages:\n%s', | |
| 183 '\n'.join(map(str, results.pages_that_failed))) | |
| 184 raise Exception('ExtensionsProfileCreator failed.') | |
| 185 | |
| 108 def _PrepareExtensionInstallFiles(self): | 186 def _PrepareExtensionInstallFiles(self): |
| 109 """Download extension archives and create extension install files.""" | 187 """Download extension archives and create extension install files.""" |
| 110 extensions_to_install = self._extensions_to_install | 188 extensions_to_install = self._extensions_to_install |
| 111 if self._theme_to_install: | 189 if self._theme_to_install: |
| 112 extensions_to_install = extensions_to_install + [self._theme_to_install] | 190 extensions_to_install = extensions_to_install + [self._theme_to_install] |
| 113 num_extensions = len(extensions_to_install) | 191 num_extensions = len(extensions_to_install) |
| 114 if not num_extensions: | 192 if not num_extensions: |
| 115 raise ValueError("No extensions or themes to install:", | 193 raise ValueError("No extensions or themes to install:", |
| 116 extensions_to_install) | 194 extensions_to_install) |
| 117 | 195 |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 144 for filename in self._files_to_cleanup: | 222 for filename in self._files_to_cleanup: |
| 145 os.remove(filename) | 223 os.remove(filename) |
| 146 | 224 |
| 147 if self._extension_download_dir: | 225 if self._extension_download_dir: |
| 148 # Simple sanity check to lessen the impact of a stray rmtree(). | 226 # Simple sanity check to lessen the impact of a stray rmtree(). |
| 149 if len(self._extension_download_dir.split(os.sep)) < 3: | 227 if len(self._extension_download_dir.split(os.sep)) < 3: |
| 150 raise Exception("Path too shallow: %s" % self._extension_download_dir) | 228 raise Exception("Path too shallow: %s" % self._extension_download_dir) |
| 151 shutil.rmtree(self._extension_download_dir) | 229 shutil.rmtree(self._extension_download_dir) |
| 152 self._extension_download_dir = None | 230 self._extension_download_dir = None |
| 153 | 231 |
| 154 def CustomizeBrowserOptions(self, options): | |
| 155 self._output_profile_path = options.output_profile_path | |
| 156 | |
| 157 def WillRunTest(self, options): | |
| 158 """Run before browser starts. | |
| 159 | |
| 160 Download extensions and write installation files.""" | |
| 161 super(ExtensionsProfileCreator, self).WillRunTest(options) | |
| 162 | |
| 163 # Running this script on a corporate network or other managed environment | |
| 164 # could potentially alter the profile contents. | |
| 165 hostname = socket.gethostname() | |
| 166 if hostname.endswith('corp.google.com'): | |
| 167 raise Exception("It appears you are connected to a corporate network " | |
| 168 "(hostname=%s). This script needs to be run off the corp " | |
| 169 "network." % hostname) | |
| 170 | |
| 171 prompt = ("\n!!!This script must be run on a fresh OS installation, " | |
| 172 "disconnected from any corporate network. Are you sure you want to " | |
| 173 "continue? (y/N) ") | |
| 174 if (raw_input(prompt).lower() != 'y'): | |
| 175 sys.exit(-1) | |
| 176 self._PrepareExtensionInstallFiles() | |
| 177 | |
| 178 def DidRunTest(self, browser, results): | |
| 179 """Run before exit.""" | |
| 180 super(ExtensionsProfileCreator, self).DidRunTest() | |
| 181 # Do some basic sanity checks to make sure the profile is complete. | |
| 182 installed_extensions = browser.extensions.keys() | |
| 183 if not len(installed_extensions) == len(self._extensions_to_install): | |
| 184 # Diagnosing errors: | |
| 185 # Too many extensions: Managed environment may be installing additional | |
| 186 # extensions. | |
| 187 raise Exception("Unexpected number of extensions installed in browser", | |
| 188 installed_extensions) | |
| 189 | |
| 190 # Check that files on this list exist and have content. | |
| 191 expected_files = [ | |
| 192 os.path.join('Default', 'Network Action Predictor')] | |
| 193 for filename in expected_files: | |
| 194 filename = os.path.join(self._output_profile_path, filename) | |
| 195 if not os.path.getsize(filename) > 0: | |
| 196 raise Exception("Profile not complete: %s is zero length." % filename) | |
| 197 | |
| 198 self._CleanupExtensionInstallFiles() | |
| 199 | |
| 200 def CanRunForPage(self, page): | |
| 201 # No matter how many pages in the pageset, just perform two test iterations. | |
| 202 return page.page_set.pages.index(page) < 2 | |
| 203 | |
| 204 def ValidateAndMeasurePage(self, _, tab, results): | |
| 205 # Profile setup works in 2 phases: | |
| 206 # Phase 1: When the first page is loaded: we wait for a timeout to allow | |
| 207 # all extensions to install and to prime safe browsing and other | |
| 208 # caches. Extensions may open tabs as part of the install process. | |
| 209 # Phase 2: When the second page loads, user_story_runner closes all tabs - | |
| 210 # we are left with one open tab, wait for that to finish loading. | |
| 211 | |
| 212 # Sleep for a bit to allow safe browsing and other data to load + | |
| 213 # extensions to install. | |
| 214 if not self._extensions_installed: | |
| 215 sleep_seconds = 5 * 60 | |
| 216 logging.info("Sleeping for %d seconds." % sleep_seconds) | |
| 217 time.sleep(sleep_seconds) | |
| 218 self._extensions_installed = True | |
| 219 else: | |
| 220 # Phase 2: Wait for tab to finish loading. | |
| 221 for i in xrange(len(tab.browser.tabs)): | |
| 222 t = tab.browser.tabs[i] | |
| 223 t.WaitForDocumentReadyStateToBeComplete() | |
| OLD | NEW |