| # 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) |