| #!/usr/bin/python |
| # Copyright (c) 2012 The Native Client Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import optparse |
| import os |
| import re |
| import sys |
| import tempfile |
| |
| sys.path.append(os.path.join(os.path.dirname(__file__), '../../')) |
| import pynacl.file_tools |
| |
| import corpus_errors |
| import corpus_utils |
| |
| |
| def TestAppStartup(options, crx_path, app_path, profile_path): |
| """Run the validator on a nexe, check if the result is expected. |
| |
| Args: |
| options: bag of options. |
| crx_path: path to the crx. |
| app_path: path to the extracted crx. |
| profile_path: path to a temporary profile dir. |
| """ |
| expected_result = corpus_errors.ExpectedCrxResult(crx_path) |
| try: |
| manifest = corpus_utils.LoadManifest(app_path) |
| start_path = manifest.get('app').get('launch').get('local_path') |
| except: |
| if expected_result.Matches(corpus_errors.BAD_MANIFEST): |
| return True |
| else: |
| print "'%s': %s," % ( |
| corpus_utils.Sha1FromFilename(crx_path), corpus_errors.BAD_MANIFEST) |
| return False |
| start_url = 'chrome-extension://%s/%s' % ( |
| corpus_utils.ChromeAppIdFromPath(app_path), start_path) |
| cmd = [options.browser, |
| '--disable-web-resources', |
| '--disable-preconnect', |
| '--no-first-run', |
| '--no-default-browser-check', |
| '--enable-logging', |
| '--log-level=1', |
| '--safebrowsing-disable-auto-update', |
| '--enable-nacl', |
| '--load-extension=' + app_path, |
| '--user-data-dir=' + profile_path, start_url] |
| process_stdout, process_stderr, retcode = corpus_utils.RunWithTimeout( |
| cmd, options.duration) |
| # Check for errors we don't like. |
| result = corpus_errors.GOOD |
| errs = re.findall(':ERROR:[^\n]+', process_stderr) |
| for err in errs: |
| if 'extension_prefs.cc' in err or 'gles2_cmd_decoder.cc' in err: |
| continue |
| if 'Extension error: Could not load extension from' in err: |
| result = result.Merge(corpus_errors.COULD_NOT_LOAD) |
| continue |
| if 'NaCl process exited with' in process_stderr: |
| result = result.Merge(corpus_errors.MODULE_CRASHED) |
| continue |
| result = result.Merge(corpus_errors.CrxResult( |
| 'custom', custom=err, precidence=20)) |
| if 'NaClMakePcrelThunk:' not in process_stderr: |
| result = result.Merge(corpus_errors.MODULE_DIDNT_START) |
| # Check if result is what we expect. |
| if not expected_result.Matches(result): |
| print "'%s': %s," % (corpus_utils.Sha1FromFilename(crx_path), result) |
| if options.verbose: |
| print '-' * 70 |
| print 'Return code: %s' % retcode |
| print '>>> STDOUT' |
| print process_stdout |
| print '>>> STDERR' |
| print process_stderr |
| print '-' * 70 |
| return False |
| return True |
| |
| |
| def TestApps(options, work_dir): |
| """Test a browser on a corpus of crxs. |
| |
| Args: |
| options: bag of options. |
| work_dir: directory to operate in. |
| """ |
| profile_path = os.path.join(work_dir, 'profile_temp') |
| app_path = os.path.join(work_dir, 'app_temp') |
| |
| list_filename = os.path.join(work_dir, 'naclapps.all') |
| filenames = corpus_utils.DownloadCorpusList(list_filename) |
| filenames = [f for f in filenames if f.endswith('.crx')] |
| progress = corpus_utils.Progress(len(filenames)) |
| for filename in filenames: |
| progress.Tally() |
| corpus_utils.PrimeCache(options.cache_dir, filename) |
| # Stop here if downloading only. |
| if options.download_only: |
| continue |
| # Stop here if isn't a bad app but testing only bad apps. |
| if options.bad_only and corpus_errors.ExpectedCrxResult(filename) == 'GOOD': |
| continue |
| # Unzip the app. |
| corpus_utils.ExtractFromCache(options.cache_dir, filename, app_path) |
| try: |
| progress.Result( |
| TestAppStartup(options, filename, app_path, profile_path)) |
| finally: |
| pynacl.file_tools.RemoveDirectoryIfPresent(app_path) |
| pynacl.file_tools.RemoveDirectoryIfPresent(profile_path) |
| progress.Summary() |
| |
| |
| def Main(): |
| # TODO(bradnelson): Stderr scraping doesn't work on windows, find another |
| # way if we want to run there. |
| if sys.platform in ['cygwin', 'win32']: |
| raise Exception('Platform not yet supported') |
| |
| parser = optparse.OptionParser() |
| parser.add_option( |
| '--download-only', dest='download_only', |
| default=False, action='store_true', |
| help='download to cache without running the tests') |
| parser.add_option( |
| '--duration', dest='duration', default=30, |
| help='how long to run each app for') |
| parser.add_option( |
| '--browser', dest='browser', |
| help='browser to run') |
| parser.add_option( |
| '-v', '--verbose', dest='verbose', default=False, action='store_true', |
| help='run in verbose mode') |
| parser.add_option( |
| '--bad-only', dest='bad_only', default=False, action='store_true', |
| help='test only known bad apps') |
| parser.add_option( |
| '--cache-dir', dest='cache_dir', |
| default=corpus_utils.DefaultCacheDirectory(), |
| help='directory to cache downloads in') |
| options, args = parser.parse_args() |
| if args: |
| parser.error('unused arguments') |
| if not options.download_only: |
| if not options.browser: |
| parser.error('no browser specified') |
| |
| work_dir = tempfile.mkdtemp(suffix='startup_crxs', prefix='tmp') |
| work_dir = os.path.realpath(work_dir) |
| try: |
| TestApps(options, work_dir) |
| finally: |
| pynacl.file_tools.RemoveDirectoryIfPresent(work_dir) |
| |
| |
| if __name__ == '__main__': |
| Main() |