blob: 21a4d5890db8ec1eb6aebc5d562a979518713299 [file] [log] [blame]
# 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
# Running the HTTP server requires starting Chrome with
# init_network_controller set to True.
CHROME_KWARGS = {'extension_paths': [constants.AUDIO_TEST_EXTENSION,
'autotest_ext': True,
'init_network_controller': True}
# Local WebGL tarballs to populate the webroot.
STATIC_CONTENT = ['webgl_aquarium_static.tar.bz2',
# Client directory for the root of the HTTP server
# Paths to later convert to URLs
CLIENT_TEST_ROOT + '/webgl_aquarium_static/aquarium.html'
WEBGL_BLOB_PATH = CLIENT_TEST_ROOT + '/webgl_blob_static/blob.html'
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')
{'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)],
[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, 'r') as tar:
# Send bundle to client. The extra slash is to send directory contents.'mkdir -p {}'.format(self.CLIENT_TEST_ROOT))
self._host.send_file(temp_bundle_dir + '/', self.CLIENT_TEST_ROOT,
# Start the HTTP server
res = self._browser_facade.set_http_server_directories(
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,
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.
# 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,
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(
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)
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._display_facade = factory.create_display_facade()
self._graphics_facade = factory.create_graphics_facade()'Preparing local WebGL test assets.')
chameleon_board = host.chameleon
finder = chameleon_port_finder.ChameleonVideoInputFinder(
chameleon_board, self._display_facade)
# Snapshot the DUT system logs for any prior GPU hangs
for chameleon_port in finder.iterate_all_ports():'Setting Chameleon screen to extended mode.')
time.sleep(self.WAIT_AFTER_SWITCH)'Launching WebGL windows.')
self._setup_windows()'Measuring the external display update rate.')
self._measure_external_display_fps(chameleon_port)'Running test for {}s.'.format(test_duration))
# Raise an error on new GPU hangs
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)