223 lines
9.3 KiB
Python
223 lines
9.3 KiB
Python
# 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)
|