Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(62)

Side by Side Diff: experimental/telemetry_mini/android_go_stories.py

Issue 3002943002: [telemetry_mini] Add story runner options (Closed)
Patch Set: just --repeat Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | experimental/telemetry_mini/telemetry_mini.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2017 The Chromium Authors. All rights reserved. 2 # Copyright 2017 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 import argparse 6 import argparse
7 import fnmatch
7 import logging 8 import logging
8 import os 9 import os
9 import sys 10 import sys
10 11
11 import telemetry_mini 12 import telemetry_mini
12 13
13 14
14 BROWSER_FLAGS = [ 15 BROWSER_FLAGS = [
15 '--enable-remote-debugging', 16 '--enable-remote-debugging',
16 '--disable-fre', 17 '--disable-fre',
17 '--no-default-browser-check', 18 '--no-default-browser-check',
18 '--no-first-run', 19 '--no-first-run',
19 ] 20 ]
20 21
21 TRACE_CONFIG = { 22 TRACE_CONFIG = {
22 'excludedCategories': ['*'], 23 'excludedCategories': ['*'],
23 'includedCategories': ['rails', 'toplevel', 'startup', 'blink.user_timing'], 24 'includedCategories': ['rails', 'toplevel', 'startup', 'blink.user_timing'],
24 'memoryDumpConfig': {'triggers': []} 25 'memoryDumpConfig': {'triggers': []}
25 } 26 }
26 27
27 BROWSERS = { 28 BROWSERS = {
28 'android-chrome': telemetry_mini.ChromeApp, 29 'android-chrome': telemetry_mini.ChromeApp,
29 'android-chromium': telemetry_mini.ChromiumApp, 30 'android-chromium': telemetry_mini.ChromiumApp,
30 'android-system-chrome': telemetry_mini.SystemChromeApp, 31 'android-system-chrome': telemetry_mini.SystemChromeApp,
31 } 32 }
32 33
33 34
34 class ProcessWatcher(object):
35 def __init__(self, device):
36 self.device = device
37 self._process_pid = {}
38
39 def StartWatching(self, process_name):
40 """Register a process or android app to keep track of its PID."""
41 if isinstance(process_name, telemetry_mini.AndroidApp):
42 process_name = process_name.PACKAGE_NAME
43
44 @telemetry_mini.RetryOn(returns_falsy=True)
45 def GetPids():
46 # Returns an empty list if the process name is not found.
47 return self.device.ProcessStatus()[process_name]
48
49 assert process_name not in self._process_pid
50 pids = GetPids()
51 assert pids, 'PID for %s not found' % process_name
52 assert len(pids) == 1, 'Single PID for %s expected, but found: %s' % (
53 process_name, pids)
54 logging.info('Started watching %s (PID=%d)', process_name, pids[0])
55 self._process_pid[process_name] = pids[0]
56
57 def AssertAllAlive(self):
58 """Check that all watched processes remain alive and were not restarted."""
59 status = self.device.ProcessStatus()
60 all_alive = True
61 for process_name, old_pid in sorted(self._process_pid.iteritems()):
62 new_pids = status[process_name]
63 if not new_pids:
64 all_alive = False
65 logging.error('Process %s died (PID=%d).', process_name, old_pid)
66 elif new_pids != [old_pid]:
67 all_alive = False
68 logging.error(
69 'Process %s restarted (PID=%d -> %s).', process_name,
70 old_pid, new_pids)
71 else:
72 logging.info('Process %s still alive (PID=%d)', process_name, old_pid)
73 assert all_alive, 'Some watched processes died or got restarted'
74
75
76 def EnsureSingleBrowser(device, browser_name, force_install=False):
77 """Ensure a single Chrome browser is installed and available on the device.
78
79 Having more than one Chrome browser available may produce results which are
80 confusing or unreliable (e.g. unclear which browser will respond by default
81 to intents triggered by other apps).
82
83 This function ensures only the selected browser is available, installing it
84 if necessary, and uninstalling/disabling others.
85 """
86 browser = BROWSERS[browser_name](device)
87 available_browsers = set(device.ListPackages('chrome', only_enabled=True))
88
89 # Install or enable if needed.
90 if force_install or browser.PACKAGE_NAME not in available_browsers:
91 browser.Install()
92
93 # Uninstall disable other browser apps.
94 for other_browser in BROWSERS.itervalues():
95 if (other_browser.PACKAGE_NAME != browser.PACKAGE_NAME and
96 other_browser.PACKAGE_NAME in available_browsers):
97 other_browser(device).Uninstall()
98
99 # Finally check that only the selected browser is actually available.
100 available_browsers = device.ListPackages('chrome', only_enabled=True)
101 assert browser.PACKAGE_NAME in available_browsers, (
102 'Unable to make %s available' % browser.PACKAGE_NAME)
103 available_browsers.remove(browser.PACKAGE_NAME)
104 assert not available_browsers, (
105 'Other browsers may intefere with the test: %s' % available_browsers)
106 return browser
107
108
109 class TwitterApp(telemetry_mini.AndroidApp): 35 class TwitterApp(telemetry_mini.AndroidApp):
110 PACKAGE_NAME = 'com.twitter.android' 36 PACKAGE_NAME = 'com.twitter.android'
111 37
112 38
113 class InstagramApp(telemetry_mini.AndroidApp): 39 class InstagramApp(telemetry_mini.AndroidApp):
114 PACKAGE_NAME = 'com.instagram.android' 40 PACKAGE_NAME = 'com.instagram.android'
115 41
116 42
117 class TwitterFlipkartStory(telemetry_mini.UserStory): 43 class TwitterFlipkartStory(telemetry_mini.UserStory):
44 """Load Chrome Custom Tab from another application.
45
46 The flow of the story is:
47 - Start Twitter app to view the @flipkart profile.
48 - Tap on a link to open Flipkart in a Chrome Custom Tab.
49 - Return to Twitter app.
50 """
51 NAME = 'twitter_flipkart'
118 FLIPKART_TWITTER_LINK = [ 52 FLIPKART_TWITTER_LINK = [
119 ('package', 'com.twitter.android'), 53 ('package', 'com.twitter.android'),
120 ('class', 'android.widget.TextView'), 54 ('class', 'android.widget.TextView'),
121 ('text', 'flipkart.com') 55 ('text', 'flipkart.com')
122 ] 56 ]
123 57
124 def __init__(self, *args, **kwargs): 58 def __init__(self, *args, **kwargs):
125 super(TwitterFlipkartStory, self).__init__(*args, **kwargs) 59 super(TwitterFlipkartStory, self).__init__(*args, **kwargs)
126 self.watcher = ProcessWatcher(self.device) 60 self.watcher = ProcessWatcher(self.device)
127 self.twitter = TwitterApp(self.device) 61 self.twitter = TwitterApp(self.device)
(...skipping 14 matching lines...) Expand all
142 76
143 # Return to Twitter app. 77 # Return to Twitter app.
144 self.actions.GoBack() 78 self.actions.GoBack()
145 self.watcher.AssertAllAlive() 79 self.watcher.AssertAllAlive()
146 80
147 def RunCleanupSteps(self): 81 def RunCleanupSteps(self):
148 self.twitter.ForceStop() 82 self.twitter.ForceStop()
149 83
150 84
151 class FlipkartInstagramStory(telemetry_mini.UserStory): 85 class FlipkartInstagramStory(telemetry_mini.UserStory):
86 """Interaction between Chrome, PWAs and a WebView-based app.
87
88 The flow of the story is:
89 - Launch the Flipkart PWA.
90 - Go back home and launch the Instagram app.
91 - Use the app switcher to return to Flipkart.
92 - Go back home and launch Cricbuzz from a shortcut.
93 """
94 NAME = 'flipkart_instagram'
95
152 def __init__(self, *args, **kwargs): 96 def __init__(self, *args, **kwargs):
153 super(FlipkartInstagramStory, self).__init__(*args, **kwargs) 97 super(FlipkartInstagramStory, self).__init__(*args, **kwargs)
154 self.watcher = ProcessWatcher(self.device) 98 self.watcher = ProcessWatcher(self.device)
155 self.instagram = InstagramApp(self.device) 99 self.instagram = InstagramApp(self.device)
156 100
157 def RunPrepareSteps(self): 101 def RunPrepareSteps(self):
158 self.instagram.ForceStop() 102 self.instagram.ForceStop()
159 self.actions.ClearRecentApps() 103 self.actions.ClearRecentApps()
160 104
161 def RunStorySteps(self): 105 def RunStorySteps(self):
(...skipping 18 matching lines...) Expand all
180 self.actions.GoHome() 124 self.actions.GoHome()
181 self.actions.TapHomeScreenShortcut('Cricbuzz') 125 self.actions.TapHomeScreenShortcut('Cricbuzz')
182 self.browser.WaitForCurrentPageReady() 126 self.browser.WaitForCurrentPageReady()
183 self.actions.SwipeUp() 127 self.actions.SwipeUp()
184 self.watcher.AssertAllAlive() 128 self.watcher.AssertAllAlive()
185 129
186 def RunCleanupSteps(self): 130 def RunCleanupSteps(self):
187 self.instagram.ForceStop() 131 self.instagram.ForceStop()
188 132
189 133
134 STORIES = (
135 TwitterFlipkartStory,
136 FlipkartInstagramStory
137 )
138
139
140 class ProcessWatcher(object):
141 def __init__(self, device):
142 self.device = device
143 self._process_pid = {}
144
145 def StartWatching(self, process_name):
146 """Register a process or android app to keep track of its PID."""
147 if isinstance(process_name, telemetry_mini.AndroidApp):
148 process_name = process_name.PACKAGE_NAME
149
150 @telemetry_mini.RetryOn(returns_falsy=True)
151 def GetPids():
152 # Returns an empty list if the process name is not found.
153 return self.device.ProcessStatus()[process_name]
154
155 assert process_name not in self._process_pid
156 pids = GetPids()
157 assert pids, 'PID for %s not found' % process_name
158 assert len(pids) == 1, 'Single PID for %s expected, but found: %s' % (
159 process_name, pids)
160 logging.info('Started watching %s (PID=%d)', process_name, pids[0])
161 self._process_pid[process_name] = pids[0]
162
163 def AssertAllAlive(self):
164 """Check that all watched processes remain alive and were not restarted."""
165 status = self.device.ProcessStatus()
166 all_alive = True
167 for process_name, old_pid in sorted(self._process_pid.iteritems()):
168 new_pids = status[process_name]
169 if not new_pids:
170 all_alive = False
171 logging.error('Process %s died (PID=%d).', process_name, old_pid)
172 elif new_pids != [old_pid]:
173 all_alive = False
174 logging.error(
175 'Process %s restarted (PID=%d -> %s).', process_name,
176 old_pid, new_pids)
177 else:
178 logging.info('Process %s still alive (PID=%d)', process_name, old_pid)
179 assert all_alive, 'Some watched processes died or got restarted'
180
181
182 def EnsureSingleBrowser(device, browser_name, force_install=False):
183 """Ensure a single Chrome browser is installed and available on the device.
184
185 Having more than one Chrome browser available may produce results which are
186 confusing or unreliable (e.g. unclear which browser will respond by default
187 to intents triggered by other apps).
188
189 This function ensures only the selected browser is available, installing it
190 if necessary, and uninstalling/disabling others.
191 """
192 browser = BROWSERS[browser_name](device)
193 available_browsers = set(device.ListPackages('chrome', only_enabled=True))
194
195 # Install or enable if needed.
196 if force_install or browser.PACKAGE_NAME not in available_browsers:
197 browser.Install()
198
199 # Uninstall disable other browser apps.
200 for other_browser in BROWSERS.itervalues():
201 if (other_browser.PACKAGE_NAME != browser.PACKAGE_NAME and
202 other_browser.PACKAGE_NAME in available_browsers):
203 other_browser(device).Uninstall()
204
205 # Finally check that only the selected browser is actually available.
206 available_browsers = device.ListPackages('chrome', only_enabled=True)
207 assert browser.PACKAGE_NAME in available_browsers, (
208 'Unable to make %s available' % browser.PACKAGE_NAME)
209 available_browsers.remove(browser.PACKAGE_NAME)
210 assert not available_browsers, (
211 'Other browsers may intefere with the test: %s' % available_browsers)
212 return browser
213
214
190 def main(): 215 def main():
191 browser_names = sorted(BROWSERS) 216 browser_names = sorted(BROWSERS)
192 default_browser = 'android-chrome' 217 default_browser = 'android-chrome'
193 parser = argparse.ArgumentParser() 218 parser = argparse.ArgumentParser()
194 parser.add_argument('--serial', 219 parser.add_argument('--serial',
195 help='device serial on which to run user stories' 220 help='device serial on which to run user stories'
196 ' (defaults to first device found)') 221 ' (defaults to first device found)')
197 parser.add_argument('--adb-bin', default='adb', metavar='PATH', 222 parser.add_argument('--adb-bin', default='adb', metavar='PATH',
198 help='path to adb binary to use (default: %(default)s)') 223 help='path to adb binary to use (default: %(default)s)')
199 parser.add_argument('--browser', default=default_browser, metavar='NAME', 224 parser.add_argument('--browser', default=default_browser, metavar='NAME',
200 choices=browser_names, 225 choices=browser_names,
201 help='one of: %s' % ', '.join( 226 help='one of: %s' % ', '.join(
202 '%s (default)' % b if b == default_browser else b 227 '%s (default)' % b if b == default_browser else b
203 for b in browser_names)) 228 for b in browser_names))
229 parser.add_argument('--story-filter', metavar='PATTERN', default='*',
230 help='run the matching stories only (allows Unix'
231 ' shell-style wildcards)')
232 parser.add_argument('--repeat', metavar='NUM', type=int, default=1,
233 help='repeat the story set a number of times'
234 ' (default: %(default)d)')
235 parser.add_argument('--output-dir', metavar='PATH',
236 help='path to directory for placing output trace files'
237 ' (defaults to current directory)')
204 parser.add_argument('--force-install', action='store_true', 238 parser.add_argument('--force-install', action='store_true',
205 help='install APK even if browser is already available') 239 help='install APK even if browser is already available')
206 parser.add_argument('--apks-dir', metavar='PATH', 240 parser.add_argument('--apks-dir', metavar='PATH',
207 help='path where to find APKs to install') 241 help='path where to find APKs to install')
208 parser.add_argument('--port', type=int, default=1234, 242 parser.add_argument('--port', type=int, default=1234,
209 help='port for connection with device' 243 help='port for connection with device'
210 ' (default: %(default)s)') 244 ' (default: %(default)s)')
211 parser.add_argument('-v', '--verbose', action='store_true') 245 parser.add_argument('-v', '--verbose', action='store_true')
212 args = parser.parse_args() 246 args = parser.parse_args()
213 247
214 logging.basicConfig() 248 logging.basicConfig()
215 if args.verbose: 249 if args.verbose:
216 logging.getLogger().setLevel(logging.INFO) 250 logging.getLogger().setLevel(logging.INFO)
217 251
252 stories = [s for s in STORIES if fnmatch.fnmatch(s.NAME, args.story_filter)]
253 if not stories:
254 return 'No matching stories'
255
256 if args.output_dir is None:
257 args.output_dir = os.getcwd()
258 else:
259 args.output_dir = os.path.realpath(args.output_dir)
260 if not os.path.isdir(args.output_dir):
261 return 'Output directory does not exit'
262
218 if args.apks_dir is None: 263 if args.apks_dir is None:
219 args.apks_dir = os.path.realpath(os.path.join( 264 args.apks_dir = os.path.realpath(os.path.join(
220 os.path.dirname(__file__), '..', '..', '..', '..', 265 os.path.dirname(__file__), '..', '..', '..', '..',
221 'out', 'Release', 'apks')) 266 'out', 'Release', 'apks'))
222 telemetry_mini.AndroidApp.APKS_DIR = args.apks_dir 267 telemetry_mini.AndroidApp.APKS_DIR = args.apks_dir
223 268
224 telemetry_mini.AdbMini.ADB_BIN = args.adb_bin 269 telemetry_mini.AdbMini.ADB_BIN = args.adb_bin
225 if args.serial is None: 270 if args.serial is None:
226 device = next(telemetry_mini.AdbMini.GetDevices()) 271 device = next(telemetry_mini.AdbMini.GetDevices())
227 logging.warning('Connected to first device found: %s', device.serial) 272 logging.warning('Connected to first device found: %s', device.serial)
228 else: 273 else:
229 device = telemetry_mini.AdbMini(args.serial) 274 device = telemetry_mini.AdbMini(args.serial)
230 275
231 # Some operations may require a rooted device. 276 # Some operations may require a rooted device.
232 device.RunCommand('root') 277 device.RunCommand('root')
233 device.RunCommand('wait-for-device') 278 device.RunCommand('wait-for-device')
234 279
235 browser = EnsureSingleBrowser(device, args.browser, args.force_install) 280 browser = EnsureSingleBrowser(device, args.browser, args.force_install)
281 browser.SetBrowserFlags(BROWSER_FLAGS)
282 browser.SetTraceConfig(TRACE_CONFIG)
236 browser.SetDevToolsLocalPort(args.port) 283 browser.SetDevToolsLocalPort(args.port)
237 284 telemetry_mini.RunStories(browser, stories, args.repeat, args.output_dir)
238 story = FlipkartInstagramStory(browser)
239 story.Run(BROWSER_FLAGS, TRACE_CONFIG, 'trace.json')
240 285
241 286
242 if __name__ == '__main__': 287 if __name__ == '__main__':
243 sys.exit(main()) 288 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | experimental/telemetry_mini/telemetry_mini.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698