普通文本  |  223行  |  9.32 KB

# Copyright 2018 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.

"""Test multiple WebGL windows spread across internal and external displays."""

import collections
import logging
import os
import tarfile
import time

from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import constants
from autotest_lib.client.cros.chameleon import chameleon_port_finder
from autotest_lib.client.cros.chameleon import chameleon_screen_test
from autotest_lib.server import test
from autotest_lib.server import utils
from autotest_lib.server.cros.multimedia import remote_facade_factory


class graphics_MultipleDisplays(test.test):
    """Loads multiple WebGL windows on internal and external displays.

    This test first initializes the extended Chameleon display. It then
    launches four WebGL windows, two on each display.
    """
    version = 1
    WAIT_AFTER_SWITCH = 5
    FPS_MEASUREMENT_DURATION = 15
    STUCK_FPS_THRESHOLD = 2
    MAXIMUM_STUCK_MEASUREMENTS = 5

    # Running the HTTP server requires starting Chrome with
    # init_network_controller set to True.
    CHROME_KWARGS = {'extension_paths': [constants.AUDIO_TEST_EXTENSION,
                                          constants.DISPLAY_TEST_EXTENSION],
                     'autotest_ext': True,
                     'init_network_controller': True}

    # Local WebGL tarballs to populate the webroot.
    STATIC_CONTENT = ['webgl_aquarium_static.tar.bz2',
                      'webgl_blob_static.tar.bz2']
    # Client directory for the root of the HTTP server
    CLIENT_TEST_ROOT = \
        '/usr/local/autotest/tests/graphics_MultipleDisplays/webroot'
    # Paths to later convert to URLs
    WEBGL_AQUARIUM_PATH = \
        CLIENT_TEST_ROOT + '/webgl_aquarium_static/aquarium.html'
    WEBGL_BLOB_PATH = CLIENT_TEST_ROOT + '/webgl_blob_static/blob.html'

    MEDIA_CONTENT_BASE = ('https://commondatastorage.googleapis.com'
                          '/chromiumos-test-assets-public')
    H264_URL = MEDIA_CONTENT_BASE + '/Shaka-Dash/1080_60.mp4'
    VP9_URL = MEDIA_CONTENT_BASE + '/Shaka-Dash/1080_60.webm'

    # Simple configuration to capture window position, content URL, or local
    # path. Positioning is either internal or external and left or right half
    # of the display. As an example, to open the newtab page on the left
    # half: WindowConfig(True, True, 'chrome://newtab', None).
    WindowConfig = collections.namedtuple(
        'WindowConfig', 'internal_display, snap_left, url, path')

    WINDOW_CONFIGS = \
        {'aquarium+blob': [WindowConfig(True, True, None, WEBGL_AQUARIUM_PATH),
                           WindowConfig(True, False, None, WEBGL_BLOB_PATH),
                           WindowConfig(False, True, None, WEBGL_AQUARIUM_PATH),
                           WindowConfig(False, False, None, WEBGL_BLOB_PATH)],
         'aquarium+vp9+blob+h264':
              [WindowConfig(True, True, None, WEBGL_AQUARIUM_PATH),
               WindowConfig(True, False, VP9_URL, None),
               WindowConfig(False, True, None, WEBGL_BLOB_PATH),
               WindowConfig(False, False, H264_URL, None)]}


    def _prepare_test_assets(self):
        """Create a local test bundle and send it to the client.

        @raise ValueError if the HTTP server does not start.
        """
        # Create a directory to unpack archives.
        temp_bundle_dir = utils.get_tmp_dir()

        for static_content in self.STATIC_CONTENT:
            archive_path = os.path.join(self.bindir, 'files', static_content)

            with tarfile.open(archive_path, 'r') as tar:
                tar.extractall(temp_bundle_dir)

        # Send bundle to client. The extra slash is to send directory contents.
        self._host.run('mkdir -p {}'.format(self.CLIENT_TEST_ROOT))
        self._host.send_file(temp_bundle_dir + '/', self.CLIENT_TEST_ROOT,
                             delete_dest=True)

        # Start the HTTP server
        res = self._browser_facade.set_http_server_directories(
            self.CLIENT_TEST_ROOT)
        if not res:
            raise ValueError('HTTP server failed to start.')

    def _calculate_new_bounds(self, config):
        """Calculates bounds for 'snapping' to the left or right of a display.

        @param config: WindowConfig specifying which display and side.

        @return Dictionary with keys top, left, width, and height for the new
                window boundaries.
        """
        new_bounds = {'top': 0, 'left': 0, 'width': 0, 'height': 0}
        display_info = filter(
            lambda d: d.is_internal == config.internal_display,
            self._display_facade.get_display_info())
        display_info = display_info[0]

        # Since we are "snapping" windows left and right, set the width to half
        # and set the height to the full working area.
        new_bounds['width'] = int(display_info.work_area.width / 2)
        new_bounds['height'] = display_info.work_area.height

        # To specify the left or right "snap", first set the left edge to the
        # display boundary. Note that for the internal display this will be 0.
        # For the external display it will already include the offset from the
        # internal display. Finally, if we are positioning to the right half
        # of the display also add in the width.
        new_bounds['left'] = display_info.bounds.left
        if not config.snap_left:
            new_bounds['left'] = new_bounds['left'] + new_bounds['width']

        return new_bounds

    def _measure_external_display_fps(self, chameleon_port):
        """Measure the update rate of the external display.

        @param chameleon_port: ChameleonPort object for recording.

        @raise ValueError if Chameleon FPS measurements indicate the external
               display was not changing.
        """
        chameleon_port.start_capturing_video()
        time.sleep(self.FPS_MEASUREMENT_DURATION)
        chameleon_port.stop_capturing_video()

        # FPS information for saving later
        self._fps_list = chameleon_port.get_captured_fps_list()

        stuck_fps_list = filter(lambda fps: fps < self.STUCK_FPS_THRESHOLD,
                                self._fps_list)
        if len(stuck_fps_list) > self.MAXIMUM_STUCK_MEASUREMENTS:
            msg = 'Too many measurements {} are < {} FPS. GPU hang?'.format(
                self._fps_list, self.STUCK_FPS_THRESHOLD)
            raise ValueError(msg)

    def _setup_windows(self):
        """Create windows and update their positions.

        @raise ValueError if the selected subtest is not a valid configuration.
        @raise ValueError if a window configurations is invalid.
        """

        if self._subtest not in self.WINDOW_CONFIGS:
            msg = '{} is not a valid subtest. Choices are {}.'.format(
                self._subtest, self.WINDOW_CONFIGS.keys())
            raise ValueError(msg)

        for window_config in self.WINDOW_CONFIGS[self._subtest]:
            url = window_config.url
            if not url:
                if not window_config.path:
                    msg = 'Path & URL not configured. {}'.format(window_config)
                    raise ValueError(msg)

                # Convert the locally served content path to a URL.
                url =  self._browser_facade.http_server_url_of(
                    window_config.path)

            new_bounds = self._calculate_new_bounds(window_config)
            new_id = self._display_facade.create_window(url)
            self._display_facade.update_window(new_id, 'normal', new_bounds)
            time.sleep(self.WAIT_AFTER_SWITCH)

    def run_once(self, host, subtest, test_duration=60):
        self._host = host
        self._subtest = subtest

        factory = remote_facade_factory.RemoteFacadeFactory(host)
        self._browser_facade = factory.create_browser_facade()
        self._browser_facade.start_custom_chrome(self.CHROME_KWARGS)
        self._display_facade = factory.create_display_facade()
        self._graphics_facade = factory.create_graphics_facade()

        logging.info('Preparing local WebGL test assets.')
        self._prepare_test_assets()

        chameleon_board = host.chameleon
        chameleon_board.setup_and_reset(self.outputdir)
        finder = chameleon_port_finder.ChameleonVideoInputFinder(
                chameleon_board, self._display_facade)

        # Snapshot the DUT system logs for any prior GPU hangs
        self._graphics_facade.graphics_state_checker_initialize()

        for chameleon_port in finder.iterate_all_ports():
            logging.info('Setting Chameleon screen to extended mode.')
            self._display_facade.set_mirrored(False)
            time.sleep(self.WAIT_AFTER_SWITCH)

            logging.info('Launching WebGL windows.')
            self._setup_windows()

            logging.info('Measuring the external display update rate.')
            self._measure_external_display_fps(chameleon_port)

            logging.info('Running test for {}s.'.format(test_duration))
            time.sleep(test_duration)

            # Raise an error on new GPU hangs
            self._graphics_facade.graphics_state_checker_finalize()

    def postprocess_iteration(self):
        desc = 'Display update rate {}'.format(self._subtest)
        self.output_perf_value(description=desc, value=self._fps_list,
                               units='FPS', higher_is_better=True, graph=None)