# Copyright (c) 2012 The Chromium OS 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 logging import os import pprint import shutil import subprocess import sys import time from autotest_lib.client.bin import test, utils from autotest_lib.client.common_lib import error from autotest_lib.client.common_lib.cros import chrome from autotest_lib.client.cros import constants, cros_logging # The name of the Chrome OS Pepper Flash binary. _BINARY = 'libpepflashplayer.so' # The path to the system provided (read only) Flash binary. _SYSTEM_STORE = '/opt/google/chrome/pepper' # The name of the file containing metainformation for the system binary. _FLASH_INFO = 'pepper-flash.info' # The name of the component updated manifest describing version, OS, # architecture and required ppapi interfaces. _MANIFEST = 'manifest.json' # The tmp location Chrome downloads the bits from Omaha to. _DOWNLOAD_STORE = '/home/chronos/PepperFlash' # The location the CrOS component updater stores new images in. _COMPONENT_STORE = '/var/lib/imageloader/PepperFlashPlayer' # latest-version gets updated after the library in the store. We use it to # check for completion of download. _COMPONENT_STORE_LATEST = _COMPONENT_STORE + '/latest-version' # The location at which the latest component updated Flash binary is mounted # for execution. _COMPONENT_MOUNT = '/run/imageloader/PepperFlashPlayer' # Set of all possible paths at which Flash binary could be found. _FLASH_PATHS = { _SYSTEM_STORE, _DOWNLOAD_STORE, _COMPONENT_STORE, _COMPONENT_MOUNT} # Run the traditional Flash sanity check (just check that any Flash works). _CU_ACTION_SANITY = 'sanity' # Clean out all component update state (in preparation to next update). _CU_ACTION_DELETE = 'delete-component' # TODO(ihf): Implement this action to simulated component on component update. _CU_ACTION_INSTALL_OLD = 'install-old-component' # Download the latest available component from Omaha. _CU_ACTION_DOWNLOAD = 'download-omaha-component' # Using current state of DUT verify the Flash in _COMPONENT_MOUNT. _CU_ACTION_VERIFY_COMPONENT = 'verify-component-flash' # Using current state of DUT verify the Flash shipping with the system image. _CU_ACTION_VERIFY_SYSTEM = 'verify-system-flash' class desktopui_FlashSanityCheck(test.test): """ Sanity test that ensures flash instance is launched when a swf is played. """ version = 4 _messages_log_reader = None _ui_log_reader = None _test_url = None _testServer = None _time_to_wait_secs = 5 _swf_runtime = 5 _retries = 10 _component_download_timeout_secs = 300 def verify_file(self, name): """ Does sanity checks on a file on disk. @param name: filename to verify. """ if not os.path.exists(name): raise error.TestFail('Failed: File does not exist %s' % name) if not os.path.isfile(name): raise error.TestFail('Failed: Not a file %s' % name) if os.path.getsize(name) <= 0: raise error.TestFail('Failed: File is too short %s' % name) if name.endswith('libpepflashplayer.so'): output = subprocess.check_output(['file %s' % name], shell=True) if not 'stripped' in output: logging.error(output) raise error.TestFail('Failed: Flash binary not stripped.') if not 'dynamically linked' in output: logging.error(output) raise error.TestFail('Failed: Flash not dynamically linked.') arch = utils.get_arch_userspace() logging.info('get_arch_userspace = %s', arch) if arch == 'arm' and not 'ARM' in output: logging.error(output) raise error.TestFail('Failed: Flash binary not for ARM.') if arch == 'x86_64' and not 'x86-64' in output: logging.error(output) raise error.TestFail('Failed: Flash binary not for x86_64.') if arch == 'i386' and not '80386' in output: logging.error(output) raise error.TestFail('Failed: Flash binary not for i386.') logging.info('Verified file %s', name) def serve_swf_to_browser(self, browser): """ Tries to serve a sample swf to browser. A failure of this function does not imply a problem with Flash. @param browser: The Browser object to run the test with. @return: True if we managed to send swf to browser, False otherwise. """ # Prepare index.html/Trivial.swf to be served. browser.platform.SetHTTPServerDirectories(self.bindir) test_url = browser.platform.http_server.UrlOf(os.path.join(self.bindir, 'index.html')) tab = None # BUG(485108): Work around a telemetry timing out after login. try: logging.info('Getting tab from telemetry...') tab = browser.tabs[0] except: logging.warning('Unexpected exception getting tab: %s', pprint.pformat(sys.exc_info()[0])) if tab is None: return False logging.info('Initialize reading system logs.') self._messages_log_reader = cros_logging.LogReader() self._messages_log_reader.set_start_by_current() self._ui_log_reader = cros_logging.LogReader('/var/log/ui/ui.LATEST') self._ui_log_reader.set_start_by_current() logging.info('Done initializing system logs.') # Verify that the swf got pulled. try: tab.Navigate(test_url) tab.WaitForDocumentReadyStateToBeComplete() return True except: logging.warning('Unexpected exception waiting for document: %s', pprint.pformat(sys.exc_info()[0])) return False def verify_flash_process(self, load_path=None): """Verifies the Flash process runs and doesn't crash. @param load_path: The expected path of the Flash binary. If set function and Flash was loaded from a different path, function will fail the test. """ logging.info('Waiting for Pepper process.') # Verify that we see a ppapi process and assume it is Flash. ppapi = utils.wait_for_value_changed( lambda: (utils.get_process_list('chrome', '--type=ppapi')), old_value=[], timeout_sec=self._time_to_wait_secs) logging.info('ppapi process list at start: %s', ', '.join(ppapi)) if not ppapi: msg = 'flash/platform/pepper/pep_' if not self._ui_log_reader.can_find(msg): raise error.TestFail( 'Failed: Flash did not start (logs) and no ppapi process ' 'found.' ) # There is a chrome bug where the command line of the ppapi and # other processes is shown as "type=zygote". Bail out if we see more # than 2. Notice, we already did the waiting, so there is no need to # do more of it. zygote = utils.get_process_list('chrome', '--type=zygote') if len(zygote) > 2: logging.warning('Flash probably launched by Chrome as zygote: ' '<%s>.', ', '.join(zygote)) # We have a ppapi process. Let it run for a little and see if it is # still alive. logging.info('Running Flash content for a little while.') time.sleep(self._swf_runtime) logging.info('Verifying the Pepper process is still around.') ppapi = utils.wait_for_value_changed( lambda: (utils.get_process_list('chrome', '--type=ppapi')), old_value=[], timeout_sec=self._time_to_wait_secs) # Notice that we are not checking for equality of ppapi on purpose. logging.info('PPapi process list found: <%s>', ', '.join(ppapi)) # Any better pattern matching? msg = ' Received crash notification for ' + constants.BROWSER if self._messages_log_reader.can_find(msg): raise error.TestFail('Failed: Browser crashed during test.') if not ppapi: raise error.TestFail( 'Failed: Pepper process disappeared during test.') # At a minimum Flash identifies itself during process start. msg = 'flash/platform/pepper/pep_' if not self._ui_log_reader.can_find(msg): raise error.TestFail( 'Failed: Saw ppapi process but no Flash output.') # Check that libpepflashplayer.so was loaded from the expected path. if load_path: # Check all current process for Flash library. output = subprocess.check_output( ['grep libpepflashplayer.so /proc/*/maps'], shell=True) # Verify there was no other than the expected location. for dont_load_path in _FLASH_PATHS - {load_path}: if dont_load_path in output: logging.error('Flash incorrectly loaded from %s', dont_load_path) logging.info(output) raise error.TestFail('Failed: Flash incorrectly loaded ' 'from %s' % dont_load_path) logging.info('Verified Flash was indeed not loaded from %s', dont_load_path) # Verify at least one of the libraries came from where we expected. if not load_path in output: # Mystery. We saw a Flash loaded from who knows where. logging.error('Flash not loaded from %s', load_path) logging.info(output) raise error.TestFail('Failed: Flash not loaded from %s' % load_path) logging.info('Saw a flash library loaded from %s.', load_path) def action_delete_component(self): """ Deletes all components on the DUT. Notice _COMPONENT_MOUNT cannot be deleted. It will remain until after reboot of the DUT. """ if os.path.exists(_COMPONENT_STORE): shutil.rmtree(_COMPONENT_STORE) if os.path.exists(_COMPONENT_STORE): raise error.TestFail('Error: could not delete %s', _COMPONENT_STORE) if os.path.exists(_DOWNLOAD_STORE): shutil.rmtree(_DOWNLOAD_STORE) if os.path.exists(_DOWNLOAD_STORE): raise error.TestFail('Error: could not delete %s', _DOWNLOAD_STORE) def action_download_omaha_component(self): """ Pretend we have no system Flash binary and tell browser to accelerate the component update process. TODO(ihf): Is this better than pretending the system binary is old? """ # TODO(ihf): Find ways to test component updates on top of component # updates maybe by checking hashlib.md5(open(_COMPONENT_STORE_LATEST)). if os.path.exists(_COMPONENT_STORE): raise error.TestFail('Error: currently unable to test component ' 'update as component store not clean before ' 'download.') # TODO(ihf): Remove --component-updater=test-request once Finch is set # up to behave more like a user in the field. browser_args = ['--ppapi-flash-path=', '--ppapi-flash-version=0.0.0.0', '--component-updater=fast-update,test-request'] logging.info(browser_args) # Browser will download component, but it will require a subsequent # reboot by the caller to use it. (Browser restart is not enough.) with chrome.Chrome(extra_browser_args=browser_args, init_network_controller=True) as cr: self.serve_swf_to_browser(cr.browser) # Wait for the last file to be written by component updater. utils.wait_for_value_changed( lambda: (os.path.exists(_COMPONENT_STORE_LATEST)), False, timeout_sec=self._component_download_timeout_secs) if not os.path.exists(_COMPONENT_STORE): raise error.TestFail('Failed: after download no component at ' '%s' % _COMPONENT_STORE) # This may look silly but we prefer giving the system a bit more # time to write files to disk before subsequent reboot. os.system('sync') time.sleep(10) def action_install_old_component(self): """ Puts an old/mock manifest and Flash binary into _COMPONENT_STORE. """ # TODO(ihf): Implement. Problem is, mock component binaries need to be # signed by Omaha. But if we had this we could test component updating # a component update. pass def action_verify_component_flash(self): """ Verifies that the next use of Flash is from _COMPONENT_MOUNT. """ # Verify there is already a binary in the component store. self.verify_file(_COMPONENT_STORE_LATEST) # Verify that binary was mounted during boot. self.verify_file(os.path.join(_COMPONENT_MOUNT, 'libpepflashplayer.so')) self.verify_file(os.path.join(_COMPONENT_MOUNT, 'manifest.json')) # Pretend we have a really old Flash revision on system to force using # the downloaded component. browser_args = ['--ppapi-flash-version=1.0.0.0'] # Verify that Flash runs from _COMPONENT_MOUNT. self.run_flash_test( browser_args=browser_args, load_path=_COMPONENT_MOUNT) def action_verify_system_flash(self): """ Verifies that next use of Flash is from the _SYSTEM_STORE. """ # Verify there is a binary in the system store. self.verify_file(os.path.join(_SYSTEM_STORE, _BINARY)) # Enable component updates and pretend we have a really new Flash # version on the system image. browser_args = ['--ppapi-flash-version=9999.0.0.0'] # Verify that Flash runs from _SYSTEM_STORE. self.run_flash_test(browser_args=browser_args, load_path=_SYSTEM_STORE) def run_flash_test(self, browser_args=None, load_path=None): """ Verifies that directing the browser to an swf file results in a running Pepper Flash process which does not immediately crash. @param browser_args: additional browser args. @param load_path: flash load path. """ if not browser_args: browser_args = [] # This is Flash. Disable html5 by default feature. browser_args += ['--disable-features=PreferHtmlOverPlugins'] # As this is an end to end test with nontrivial setup we can expect a # certain amount of flakes which are *unrelated* to running Flash. We # try to hide these unrelated flakes by selective retry. for _ in range(0, self._retries): logging.info(browser_args) with chrome.Chrome(extra_browser_args=browser_args, init_network_controller=True) as cr: if self.serve_swf_to_browser(cr.browser): self.verify_flash_process(load_path) return raise error.TestFail( 'Error: Unable to test Flash due to setup problems.') def run_once(self, CU_action=_CU_ACTION_SANITY): """ Main entry point for desktopui_FlashSanityCheck. Performs an action as specified by control file or by the component_UpdateFlash server test. (The current need to reboot after switching to/from component binary makes this test a server test.) @param CU_action: component updater action to verify (typically called from server test). """ logging.info('+++++ desktopui_FlashSanityCheck +++++') logging.info('Performing %s', CU_action) if CU_action == _CU_ACTION_DELETE: self.action_delete_component() elif CU_action == _CU_ACTION_DOWNLOAD: self.action_download_omaha_component() elif CU_action == _CU_ACTION_INSTALL_OLD: self.action_install_old_component() elif CU_action == _CU_ACTION_SANITY: self.run_flash_test() elif CU_action == _CU_ACTION_VERIFY_COMPONENT: self.action_verify_component_flash() elif CU_action == _CU_ACTION_VERIFY_SYSTEM: self.action_verify_system_flash() else: raise error.TestError('Error: unknown action %s', CU_action)