ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / tools / perf / profile_creators / extensions_profile_creator.py
blob8f484192afa2cfe7031f69a21eeaf58ea8607297
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
3 # found in the LICENSE file.
5 import json
6 import logging
7 import os
8 import platform
9 import shutil
10 import socket
11 import sys
12 import tempfile
13 import time
14 import urllib2
15 import zipfile
17 from telemetry.page import profile_creator
19 import page_sets
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
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._user_story_set = page_sets.Typical25PageSet()
33 # No matter how many pages in the PageSet, just perform two test iterations.
34 for user_story in self._user_story_set[2:]:
35 self._user_story_set.RemoveUserStory(user_story)
37 # Have the extensions been installed yet?
38 self._extensions_installed = False
40 # Expected
41 self._expected_extension_count = 0
43 def ValidateAndMeasurePage(self, _, tab, results):
44 # Superclass override.
45 # Profile setup works in 2 phases:
46 # Phase 1: When the first page is loaded: we wait for a timeout to allow
47 # all extensions to install and to prime safe browsing and other
48 # caches. Extensions may open tabs as part of the install process.
49 # Phase 2: When the second page loads, user_story_runner closes all tabs -
50 # we are left with one open tab, wait for that to finish loading.
52 # Sleep for a bit to allow safe browsing and other data to load +
53 # extensions to install.
54 if not self._extensions_installed:
55 sleep_seconds = 15
56 logging.info("Sleeping for %d seconds." % sleep_seconds)
57 time.sleep(sleep_seconds)
58 self._extensions_installed = True
59 else:
60 # Phase 2: Wait for tab to finish loading.
61 for i in xrange(len(tab.browser.tabs)):
62 t = tab.browser.tabs[i]
63 t.WaitForDocumentReadyStateToBeComplete()
65 def DidRunTest(self, browser, results):
66 """Superclass override."""
67 super(_ExtensionPageTest, self).DidRunTest(browser,
68 results)
69 # Do some basic sanity checks to make sure the profile is complete.
70 installed_extensions = browser.extensions.keys()
71 if not len(installed_extensions) == self._expected_extension_count:
72 # Diagnosing errors:
73 # Too many extensions: Managed environment may be installing additional
74 # extensions.
75 raise Exception("Unexpected number of extensions installed in browser",
76 installed_extensions)
79 def _ExternalExtensionsPath(profile_path):
80 """Returns the OS-dependent path at which to install the extension deployment
81 files.
83 |profile_path| is the path of the profile that will be used to launch the
84 browser.
85 """
86 if platform.system() == 'Darwin':
87 return str(profile_path) + '/External Extensions'
89 else:
90 raise NotImplementedError('Extension install on %s is not yet supported' %
91 platform.system())
94 def _DownloadExtension(extension_id, output_dir):
95 """Download an extension to disk.
97 Args:
98 extension_id: the extension id.
99 output_dir: Directory to download into.
101 Returns:
102 Extension file downloaded."""
103 extension_download_path = os.path.join(output_dir, "%s.crx" % extension_id)
105 # Ideally, the Chrome version would be dynamically extracted from the binary.
106 # Instead, we use a Chrome version whose release date is expected to be
107 # about a hundred years in the future.
108 chrome_version = '1000.0.0.0'
109 extension_url = (
110 "https://clients2.google.com/service/update2/crx?response=redirect"
111 "&prodversion=%s&x=id%%3D%s%%26lang%%3Den-US%%26uc"
112 % (chrome_version, extension_id))
113 response = urllib2.urlopen(extension_url)
114 assert(response.getcode() == 200)
116 with open(extension_download_path, "w") as f:
117 f.write(response.read())
119 return extension_download_path
122 def _GetExtensionInfoFromCRX(crx_path):
123 """Parse an extension archive and return information.
125 Note:
126 The extension name returned by this function may not be valid
127 (e.g. in the case of a localized extension name). It's use is just
128 meant to be informational.
130 Args:
131 crx_path: path to crx archive to look at.
133 Returns:
134 Tuple consisting of:
135 (crx_version, extension_name)"""
136 crx_zip = zipfile.ZipFile(crx_path)
137 manifest_contents = crx_zip.read('manifest.json')
138 decoded_manifest = json.loads(manifest_contents)
139 crx_version = decoded_manifest['version']
140 extension_name = decoded_manifest['name']
142 return (crx_version, extension_name)
145 class ExtensionsProfileCreator(profile_creator.ProfileCreator):
146 """Abstract base class for profile creators that install extensions.
148 Extensions are installed using the mechanism described in
149 https://developer.chrome.com/extensions/external_extensions.html .
151 Subclasses are meant to be run interactively.
153 def __init__(self, extensions_to_install=None, theme_to_install=None):
154 super(ExtensionsProfileCreator, self).__init__()
156 # List of extensions to install.
157 self._extensions_to_install = []
158 if extensions_to_install:
159 self._extensions_to_install.extend(extensions_to_install)
160 if theme_to_install:
161 self._extensions_to_install.append(theme_to_install)
163 # Directory to download extension files into.
164 self._extension_download_dir = None
166 # List of files to delete after run.
167 self._files_to_cleanup = []
169 def Run(self, options):
170 # Installing extensions requires that the profile directory exist before
171 # the browser is launched.
172 if not options.browser_options.profile_dir:
173 options.browser_options.profile_dir = tempfile.mkdtemp()
174 options.browser_options.disable_default_apps = False
176 self._PrepareExtensionInstallFiles(options.browser_options.profile_dir)
178 expectations = test_expectations.TestExpectations()
179 results = results_options.CreateResults(
180 benchmark.BenchmarkMetadata(profile_creator.__class__.__name__),
181 options)
182 extension_page_test = _ExtensionPageTest()
183 extension_page_test._expected_extension_count = len(
184 self._extensions_to_install)
185 user_story_runner.Run(
186 extension_page_test, extension_page_test._user_story_set,
187 expectations, options, results)
189 self._CleanupExtensionInstallFiles()
191 # Check that files on this list exist and have content.
192 expected_files = [
193 os.path.join('Default', 'Network Action Predictor')]
194 for filename in expected_files:
195 filename = os.path.join(options.output_profile_path, filename)
196 if not os.path.getsize(filename) > 0:
197 raise Exception("Profile not complete: %s is zero length." % filename)
199 if results.failures:
200 logging.warning('Some pages failed.')
201 logging.warning('Failed pages:\n%s',
202 '\n'.join(map(str, results.pages_that_failed)))
203 raise Exception('ExtensionsProfileCreator failed.')
205 def _PrepareExtensionInstallFiles(self, profile_path):
206 """Download extension archives and create extension install files."""
207 extensions_to_install = self._extensions_to_install
208 if not extensions_to_install:
209 raise ValueError("No extensions or themes to install:",
210 extensions_to_install)
212 # Create external extensions path if it doesn't exist already.
213 external_extensions_dir = _ExternalExtensionsPath(profile_path)
214 if not os.path.isdir(external_extensions_dir):
215 os.makedirs(external_extensions_dir)
217 self._extension_download_dir = tempfile.mkdtemp()
219 num_extensions = len(extensions_to_install)
220 for i in range(num_extensions):
221 extension_id = extensions_to_install[i]
222 logging.info("Downloading %s - %d/%d" % (
223 extension_id, (i + 1), num_extensions))
224 extension_path = _DownloadExtension(extension_id,
225 self._extension_download_dir)
226 (version, name) = _GetExtensionInfoFromCRX(extension_path)
227 extension_info = {'external_crx' : extension_path,
228 'external_version' : version,
229 '_comment' : name}
230 extension_json_path = os.path.join(external_extensions_dir,
231 "%s.json" % extension_id)
232 with open(extension_json_path, 'w') as f:
233 f.write(json.dumps(extension_info))
234 self._files_to_cleanup.append(extension_json_path)
236 def _CleanupExtensionInstallFiles(self):
237 """Cleanup stray files before exiting."""
238 logging.info("Cleaning up stray files")
239 for filename in self._files_to_cleanup:
240 os.remove(filename)
242 if self._extension_download_dir:
243 # Simple sanity check to lessen the impact of a stray rmtree().
244 if len(self._extension_download_dir.split(os.sep)) < 3:
245 raise Exception("Path too shallow: %s" % self._extension_download_dir)
246 shutil.rmtree(self._extension_download_dir)
247 self._extension_download_dir = None