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

Side by Side Diff: tests/startup_regression/startup_regression.py

Issue 9950041: Adding a startup regression test. (Closed) Base URL: svn://svn.chromium.org/native_client/trunk/src/native_client
Patch Set: Created 8 years, 8 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import codecs
7 import hashlib
8 import json
9 import optparse
10 import os
11 import re
12 import subprocess
13 import sys
14 import tempfile
15 import threading
16 import time
17 import zipfile
18
19
20 KNOWN_BAD = set([
Nick Bray 2012/04/02 20:31:41 This is going to be visible in a publicly viewable
bradn 2012/04/02 22:43:59 So actually mainly I'm worried about app ids. Thes
21 # Bad manifest
22 '2f97cec9f13b0f774d1f49490f26f32213e4e0a5',
23 'ced1fea90b71b0a8da08c1a1e6cb35975cc84f52',
24 '3d6832749c8c1346c65b30f4b191930dec5f04a3',
25 '0937b653af5553856532454ec340d0e0075bc0b4',
26 '09ffe3793113fe564b71800a5844189c00bd8210',
27 '81a4a3de69dd4ad169b1d4a7268b44c78ea5ffa8',
28 '612a5aaa821b4b636168025f027e721c0f046e7c',
29 '14f389a8c406d60e0fc05a1ec0189a652a1f006e',
30 'a8aa42d699dbef3e1403e4fdc49325e89a91f653',
31 'c6d40d4f3c8dccc710d8c09bfd074b2d20a504d2',
32 # Bad permissions
33 '8de65668cc7280ffb70ffd2fa5b2a22112156966',
34 # Snap
35 'b458cd57c8b4e6c313b18f370fad59779f573afc',
36 # No nacl module
37 '57be161e5ff7011d2283e507a70f9005c448002b',
38 '4beecff67651f13e013c12a5bf3661041ded323c',
39 '1f861c0d8c173b64df3e70cfa1a5cd710ba59430',
40 'cfd62adf6790eed0520da2deb2246fc02e70c57e',
41 ])
42
43
44 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
45 TESTS_DIR = os.path.dirname(SCRIPT_DIR)
46 NACL_DIR = os.path.dirname(TESTS_DIR)
47
48
49 # Imports from the build directory.
50 sys.path.insert(0, os.path.join(NACL_DIR, 'build'))
51 import download_utils
Nick Bray 2012/04/02 20:31:41 Move import hackery above KNOWN_BAD - keep it grou
bradn 2012/04/02 22:43:59 Done.
52
53
54 def GsutilCopySilent(src, dst):
55 """Invoke gsutil cp, swallowing the output, with retry.
56
57 Args:
58 src: src url.
59 dst: dst path.
60 """
61 for _ in range(3):
62 env = os.environ.copy()
63 env['PATH'] = '/b/build/scripts/slave' + os.pathsep + env['PATH']
Nick Bray 2012/04/02 20:31:41 * Lift env creation out of loop - makes intent mor
bradn 2012/04/02 22:43:59 Done.
64 process = subprocess.Popen(
65 ['gsutil', 'cp', src, dst],
66 env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
67 process_stdout, process_stderr = process.communicate()
68 if process.returncode == 0:
69 return
70 print 'Unexpected return code: %s' % process.returncode
71 print '>>> STDOUT'
72 print process_stdout
73 print '>>> STDERR'
74 print process_stderr
75 print '-' * 70
76 sys.exit(1)
Nick Bray 2012/04/02 20:31:41 Very optional: I am not a huge fan of exiting insi
bradn 2012/04/02 22:43:59 Done, due to later unrelated refactor.
77
78
79 def DownloadTotalList(list_filename):
Nick Bray 2012/04/02 20:31:41 Bad name. List of what? Total of what?
bradn 2012/04/02 22:43:59 Done.
80 """Download list of all archived files.
81
82 Args:
83 list_filename: destination filename (kept around for debugging).
84 """
85 GsutilCopySilent('gs://nativeclient-snaps/naclapps.all', list_filename)
86 fh = open(list_filename)
87 filenames = fh.read().splitlines()
88 fh.close()
89 return [f for f in filenames if f.endswith('.crx')]
90
91
92 def DownloadFile(src_path, dst_filename):
Nick Bray 2012/04/02 20:31:41 Bad name. DownloadFileFromSnapshot?
bradn 2012/04/02 22:43:59 Done.
93 """Download a file from our snapshot.
94
95 Args:
96 src_path: datastore relative path to download from.
97 dst_filename: destination filename.
98 """
99 GsutilCopySilent('gs://nativeclient-snaps/%s' % src_path, dst_filename)
100
101
102 def Sha1Sum(path):
Nick Bray 2012/04/02 20:31:41 Sha1Digest? Sha1FileDigest?
bradn 2012/04/02 22:43:59 Done.
103 """Determine the sha1 hash of a file's contents given its path."""
104 m = hashlib.sha1()
105 fh = open(path, 'rb')
106 m.update(fh.read())
107 fh.close()
108 return m.hexdigest()
109
110
111 def Hex2Alpha(ch):
112 """Convert a hexadecimal digit from 0-9 / a-f to a-p.
113
114 Args:
115 ch: a character in 0-9 / a-f.
116 Returns:
117 A character in a-p.
118 """
119 if ch >= '0' and ch <= '9':
120 return chr(ord(ch) - ord('0') + ord('a'))
121 else:
122 return chr(ord(ch) + 10)
123
124
125 def ChromeAppIdFromPath(path):
126 """Converts a path to the corrisponding chrome app id.
Nick Bray 2012/04/02 20:31:41 Path, in what context? On disk? In the web store
bradn 2012/04/02 22:43:59 Done.
127
128 Args:
129 path: Path to an unpacked extension.
130 Returns:
131 A 32 character chrome extension app id.
132 """
133 hasher = hashlib.sha256()
134 hasher.update(os.path.realpath(path))
135 hexhash = hasher.hexdigest()[:32]
136 return ''.join([Hex2Alpha(ch) for ch in hexhash])
137
138
139 def TestAppStartup(options, crx_path, app_path, profile_path):
Nick Bray 2012/04/02 20:31:41 Big function. Is there a clean way to modularize
bradn 2012/04/02 22:43:59 Done.
140 """Run the validator on a nexe, check if the result is expected.
141
142 Args:
143 options: bag of options.
144 crx_path: path to the crx.
145 app_path: path to the extracted crx.
146 profile_path: path to a temporary profile dir.
147 """
148 manifest = LoadManifest(app_path)
149 start_path = manifest.get('app', {}).get('launch', {}).get('local_path')
150 if not start_path:
151 print '-' * 70
152 print 'Testing: %s' % crx_path
153 print 'Browser: %s' % options.browser
154 print 'BAD MANIFEST!'
155 print '-' * 70
156 # Halt on first failure.
157 sys.exit(1)
158 start_url = 'chrome-extension://%s/%s' % (
159 ChromeAppIdFromPath(app_path), start_path)
160 cmd = [options.browser,
161 '--enable-nacl',
162 '--load-extension=' + app_path,
163 '--user-data-dir=' + profile_path, start_url]
164 process = subprocess.Popen(cmd,
Nick Bray 2012/04/02 20:31:41 For example, this is essentially subprocess.commun
bradn 2012/04/02 22:43:59 Done.
165 stdout=subprocess.PIPE,
166 stderr=subprocess.PIPE)
167 def GatherOutput(fh, dst):
168 dst.append(fh.read())
169 # Gather stdout.
170 stdout_output = []
171 stdout_thread = threading.Thread(
172 target=GatherOutput, args=(process.stdout, stdout_output))
173 stdout_thread.setDaemon(True)
174 stdout_thread.start()
175 # Gather stderr.
176 stderr_output = []
177 stderr_thread = threading.Thread(
178 target=GatherOutput, args=(process.stderr, stderr_output))
179 stderr_thread.setDaemon(True)
180 stderr_thread.start()
181 # Wait for a small span for the app to load.
182 time.sleep(options.duration)
183 process.kill()
184 time.sleep(1)
Nick Bray 2012/04/02 20:31:41 Why sleep?
bradn 2012/04/02 22:43:59 Oops, switched to wait.
185 process.poll()
186 # Join up.
187 stdout_thread.join()
188 stderr_thread.join()
189 # Pick out result.
190 process_stdout = stdout_output[0]
Nick Bray 2012/04/02 20:31:41 Scraping the output will not work on Windows. We
bradn 2012/04/02 22:43:59 Done.
191 process_stderr = stderr_output[0]
Nick Bray 2012/04/02 20:31:41 Scraping for failiures => function (but not printi
bradn 2012/04/02 22:43:59 Added --verbose flag that does emit it.
192 # Check for errors we don't like.
193 failure = None
194 if 'NaClMakePcrelThunk:' not in process_stderr:
195 failure = 'nacl module not started'
196 if 'NaCl process exited with' in process_stderr:
197 failure = 'nacl module crashed'
198 errs = re.findall(':ERROR:[^\n]+', process_stderr)
199 for err in errs:
200 if ('extension_prefs.cc' not in err and
201 'gles2_cmd_decoder.cc' not in err):
202 failure = 'unknown error: ' + err
203 break
204 # Check if result is what we expect.
205 if failure:
206 print '-' * 70
207 print 'Testing: %s' % crx_path
208 print 'Browser: %s' % options.browser
209 print 'Failure: %s' % failure
210 print '>>> STDOUT'
211 print process_stdout
212 print '>>> STDERR'
213 print process_stderr
214 print '-' * 70
215 # Halt on first failure.
216 sys.exit(1)
217
218
219 def LoadManifest(app_path):
220 try:
221 manifest_data = codecs.open(os.path.join(app_path, 'manifest.json'),
222 'r', encoding='utf-8').read()
223 manifest_data = manifest_data.replace('\r', '')
224 manifest_data = manifest_data.replace(u'\ufeff', '')
Nick Bray 2012/04/02 20:31:41 What are these characters? Document.
bradn 2012/04/02 22:43:59 Done.
225 manifest_data = manifest_data.replace(u'\uffee', '')
226 return json.loads(manifest_data)
227 except:
228 return {}
Nick Bray 2012/04/02 20:31:41 This seems a little sketchy. Why return an empty
bradn 2012/04/02 22:43:59 Simplifies the logic of checking for a nested key
229
230
231 def IsBad(path):
232 """Checks a blacklist to decide if we should startup test this app.
233
234 Args:
235 path: path to the nexe.
236 Returns:
237 Boolean indicating if we should test this app.
238 """
239 return os.path.splitext(os.path.basename(path))[0] in KNOWN_BAD
240
241
242 def CachedPath(options, filename):
243 """Find the full path of a cached file, a cache root relative path.
244
245 Args:
246 options: bags of options.
247 filename: filename relative to the top of the download url / cache.
248 Returns:
249 Absolute path of where the file goes in the cache.
250 """
251 return os.path.join(options.cache_dir, 'nacl_startup_test_cache', filename)
252
253
254 def Sha1FromFilename(filename):
Nick Bray 2012/04/02 20:31:41 Are there functions here that you should be sharin
bradn 2012/04/02 22:43:59 Done.
255 """Get the expected sha1 of a file path.
256
257 Throughout we use the convention that files are store to a name of the form:
258 <path_to_file>/<sha1hex>[.<some_extention>]
259 This function extracts the expected sha1.
260
261 Args:
262 filename: filename to extract.
263 Returns:
264 Excepted sha1.
265 """
266 return os.path.splitext(os.path.basename(filename))[0]
267
268
269 def PrimeCache(options, filename):
Nick Bray 2012/04/02 20:31:41 Dito.
bradn 2012/04/02 22:43:59 Done.
270 """Attempt to add a file to the cache directory if its not already there.
271
272 Args:
273 options: bag of options.
274 filename: filename relative to the top of the download url / cache.
275 """
276 dpath = CachedPath(options, filename)
277 if not os.path.exists(dpath) or Sha1Sum(dpath) != Sha1FromFilename(filename):
278 # Try to make the directory, fail is ok, let the download fail instead.
279 try:
280 os.makedirs(os.path.basename(dpath))
281 except OSError:
282 pass
283 DownloadFile(filename, dpath)
284
285
286 def ExtractFromCache(options, source, dest):
287 """Extract a crx from the cache.
288
289 Args:
290 options: bag of options.
291 source: crx file to extract (cache relative).
292 dest: location to extract to.
293 """
294 assert not os.path.exists(dest)
Nick Bray 2012/04/02 20:31:41 If you have an assert, have a message to help expl
bradn 2012/04/02 22:43:59 Done.
295 dpath = CachedPath(options, source)
296 assert os.path.exists(dpath)
297 zf = zipfile.ZipFile(dpath, 'r')
298 os.makedirs(dest)
299 for info in zf.infolist():
300 tpath = os.path.join(dest, info.filename)
Nick Bray 2012/04/02 20:31:41 Pwnage: Zipfile is from an untrusted source, filen
bradn 2012/04/02 22:43:59 Done.
301 if info.filename.endswith('/'):
302 os.makedirs(tpath)
303 else:
304 zf.extract(info, dest)
305 zf.close()
306
307
308 def TestApps(options, work_dir):
309 """Test a browser on a corpus of crxs.
310
311 Args:
312 options: bag of options.
313 work_dir: directory to operate in.
314 """
315 profile_path = os.path.join(work_dir, 'profile_temp')
316 app_path = os.path.join(work_dir, 'app_temp')
317
318 list_filename = os.path.join(work_dir, 'naclapps.all')
319 filenames = DownloadTotalList(list_filename)
320 filenames = filenames[24:]
321
322 count = 0
323 start = time.time()
324 count = len(filenames)
325 for index, filename in enumerate(filenames):
326 tm = time.time()
327 if index > 0:
Nick Bray 2012/04/02 20:31:41 ETA calculation and / or header printing in functi
bradn 2012/04/02 22:43:59 Done.
328 eta = (count - index) * (tm - start) / index
329 eta_minutes = int(eta / 60)
330 eta_seconds = int(eta - eta_minutes * 60)
331 eta_str = ' (ETA %d:%02d)' % (eta_minutes, eta_seconds)
332 else:
333 eta_str = ''
334 print 'Processing %d of %d%s...' % (index + 1, count, eta_str)
335 # Skip if known bad.
336 if IsBad(filename):
337 continue
338 PrimeCache(options, filename)
339 # Stop here if downloading only.
340 if options.download_only:
341 continue
342 # Unzip the app.
343 ExtractFromCache(options, filename, app_path)
344 try:
345 TestAppStartup(options, filename, app_path, profile_path)
346 count += 1
347 finally:
348 download_utils.RemoveDir(app_path)
349 download_utils.RemoveDir(profile_path)
350 print 'Ran tests on %d of %d CRXs' % (count, len(filenames))
351 print 'SUCCESS'
352
353
354 def Main():
355 # Decide a default cache directory.
356 # Prefer /b (for the bots)
357 # Failing that, use scons-out.
358 # Failing that, use the current users's home dir.
359 default_cache_dir = '/b'
Nick Bray 2012/04/02 20:31:41 Another bit of code that might do well shared.
bradn 2012/04/02 22:43:59 Done.
360 if not os.path.isdir(default_cache_dir):
361 default_cache_dir = os.path.join(NACL_DIR, 'scons-out')
362 if not os.path.isdir(default_cache_dir):
363 default_cache_dir = os.path.expanduser('~/')
364 default_cache_dir = os.path.abspath(default_cache_dir)
365 assert os.path.isdir(default_cache_dir)
366
367 parser = optparse.OptionParser()
368 parser.add_option(
369 '--cache-dir', dest='cache_dir', default=default_cache_dir,
370 help='directory to cache downloads in')
371 parser.add_option(
372 '--download-only', dest='download_only',
373 default=False, action='store_true',
374 help='download to cache without running the tests')
375 parser.add_option(
376 '--duration', dest='duration', default=30,
377 help='how long to run each app for')
378 parser.add_option(
379 '--browser', dest='browser',
380 help='browser to run')
381 options, args = parser.parse_args()
382 if args:
383 parser.error('unused arguments')
384 if not options.download_only:
385 if not options.browser:
386 parser.error('no browser specified')
387
388 work_dir = tempfile.mkdtemp(suffix='startup_crxs', prefix='tmp')
389 work_dir = os.path.realpath(work_dir)
390 try:
391 TestApps(options, work_dir)
392 finally:
393 download_utils.RemoveDir(work_dir)
394
395
396 if __name__ == '__main__':
397 Main()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698