普通文本  |  365行  |  16.56 KB

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