243 lines
11 KiB
Python
243 lines
11 KiB
Python
# Copyright 2015 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.
|
|
|
|
"""This is a test for screen tearing using the Chameleon board."""
|
|
|
|
import logging
|
|
import time
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.cros.chameleon import chameleon_port_finder
|
|
from autotest_lib.server import test
|
|
from autotest_lib.server.cros.multimedia import remote_facade_factory
|
|
|
|
|
|
class display_Tearing(test.test):
|
|
"""Display tearing test by multi-color full screen animation.
|
|
|
|
This test talks to a Chameleon board and a DUT to set up, run, and verify
|
|
DUT behavior response to a series of multi-color full screen switch.
|
|
"""
|
|
|
|
version = 1
|
|
|
|
# Time to wait for Chameleon to save images into RAM.
|
|
# Current value is decided by experiments.
|
|
CHAMELEON_CAPTURE_WAIT_TIME_SEC = 1
|
|
|
|
# The initial background color to set for a new tab.
|
|
INITIAL_BACKGROUND_COLOR = 0xFFFFFF
|
|
|
|
# Time in seconds to wait for notation bubbles, including bubbles for
|
|
# external detection, mirror mode and fullscreen, to disappear.
|
|
NEW_PAGE_STABILIZE_TIME = 10
|
|
|
|
# 1. Since it is difficult to distinguish repeated frames
|
|
# generated from delay from real repeated frames, make
|
|
# sure that there are no successive repeated colors in
|
|
# TEST_COLOR_SEQUENCE. In fact, if so, the repeated ones
|
|
# will be discarded.
|
|
# 2. Similarly make sure that the the first element of
|
|
# TEST_COLOR_SEQUENCE is not INITIAL_BACKGROUND_COLOR.
|
|
# 3. Notice that the hash function in Chameleon used for
|
|
# checksums is weak, so it is possible to encounter
|
|
# hash collision. If it happens, an error will be raised
|
|
# during execution time of _display_and_get_checksum_table().
|
|
TEST_COLOR_SEQUENCE = [0x010000, 0x002300, 0x000045, 0x670000,
|
|
0x008900, 0x0000AB, 0xCD0000, 0x00EF00] * 20
|
|
|
|
def _open_color_sequence_tab(self, test_mirrored):
|
|
"""Sets up a new empty page for displaying color sequence.
|
|
|
|
@param test_mirrored: True to test mirrored mode. False not to.
|
|
"""
|
|
self._test_tab_descriptor = self._display_facade.load_url('about:blank')
|
|
if not test_mirrored:
|
|
self._display_facade.move_to_display(
|
|
self._display_facade.get_first_external_display_id())
|
|
self._display_facade.set_fullscreen(True)
|
|
logging.info('Waiting for the new tab to stabilize...')
|
|
time.sleep(self.NEW_PAGE_STABILIZE_TIME)
|
|
|
|
def _get_single_color_checksum(self, chameleon_port, color):
|
|
"""Gets the frame checksum of the full screen of the given color.
|
|
|
|
@param chameleon_port: A general ChameleonPort object.
|
|
@param color: the given color.
|
|
@return The frame checksum mentioned above, which is a tuple.
|
|
"""
|
|
try:
|
|
chameleon_port.start_capturing_video()
|
|
self._display_facade.load_color_sequence(self._test_tab_descriptor,
|
|
[color])
|
|
time.sleep(self.CHAMELEON_CAPTURE_WAIT_TIME_SEC)
|
|
finally:
|
|
chameleon_port.stop_capturing_video()
|
|
# Gets the checksum of the last one image.
|
|
last = chameleon_port.get_captured_frame_count() - 1
|
|
return tuple(chameleon_port.get_captured_checksums(last)[0])
|
|
|
|
def _display_and_get_checksum_table(self, chameleon_port, color_sequence):
|
|
"""Makes checksum table, which maps checksums into colors.
|
|
|
|
@param chameleon_port: A general ChameleonPort object.
|
|
@param color_sequence: the color_sequence that will be displayed.
|
|
@return A dictionary consists of (x: y), y is in color_sequence and
|
|
x is the checksum of the full screen of pure color y.
|
|
@raise an error if there is hash collision
|
|
"""
|
|
# Resets the background color to make sure the screen looks like
|
|
# what we expect.
|
|
self._reset_background_color()
|
|
checksum_table = {}
|
|
# Makes sure that INITIAL_BACKGROUND_COLOR is in checksum_table,
|
|
# or it may be misjudged as screen tearing.
|
|
color_set = set(color_sequence+[self.INITIAL_BACKGROUND_COLOR])
|
|
for color in color_set:
|
|
checksum = self._get_single_color_checksum(chameleon_port, color)
|
|
if checksum in checksum_table:
|
|
raise error.TestFail('Bad color sequence: hash collision')
|
|
checksum_table[checksum] = color
|
|
logging.info('Color %d has checksums %r', (color, checksum))
|
|
return checksum_table
|
|
|
|
def _reset_background_color(self):
|
|
"""Resets the background color for displaying test color sequence."""
|
|
self._display_facade.load_color_sequence(
|
|
self._test_tab_descriptor,
|
|
[self.INITIAL_BACKGROUND_COLOR])
|
|
|
|
def _display_and_capture(self, chameleon_port, color_sequence):
|
|
"""Displays the color sequence and captures frames by Chameleon.
|
|
|
|
@param chameleon_port: A general ChameleonPort object.
|
|
@param color_sequence: the color sequence to display.
|
|
@return (A list of checksums of captured frames,
|
|
A list of the timestamp for each switch).
|
|
"""
|
|
# Resets the background color to make sure the screen looks like
|
|
# what we expect.
|
|
self._reset_background_color()
|
|
try:
|
|
chameleon_port.start_capturing_video()
|
|
timestamp_list = (
|
|
self._display_facade.load_color_sequence(
|
|
self._test_tab_descriptor, color_sequence))
|
|
time.sleep(self.CHAMELEON_CAPTURE_WAIT_TIME_SEC)
|
|
finally:
|
|
chameleon_port.stop_capturing_video()
|
|
|
|
captured_checksums = chameleon_port.get_captured_checksums(0)
|
|
captured_checksums = [tuple(x) for x in captured_checksums]
|
|
return (captured_checksums, timestamp_list)
|
|
|
|
def _tearing_test(self, captured_checksums, checksum_table):
|
|
"""Checks whether some captured frame is teared by checking
|
|
their checksums.
|
|
|
|
@param captured_checksums: A list of checksums of captured
|
|
frames.
|
|
@param checksum_table: A dictionary of reasonable checksums.
|
|
@return True if the test passes.
|
|
"""
|
|
for checksum in captured_checksums:
|
|
if checksum not in checksum_table:
|
|
return False
|
|
return True
|
|
|
|
def _correction_test(
|
|
self, captured_color_sequence, expected_color_sequence):
|
|
"""Checks whether the color sequence is sent to Chameleon correctly.
|
|
|
|
Here are the checking steps:
|
|
1. Discard all successive repeated elements of both sequences.
|
|
2. If the first element of the captured color sequence is
|
|
INITIAL_BACKGROUND_COLOR, discard it.
|
|
3. Check whether the two sequences are equal.
|
|
|
|
@param captured_color_sequence: The sequence of colors captured by
|
|
Chameleon, each element of which
|
|
is an integer.
|
|
@param expected_color_sequence: The sequence of colors expected to
|
|
be displayed.
|
|
@return True if the test passes.
|
|
"""
|
|
def _discard_delayed_frames(sequence):
|
|
return [sequence[i]
|
|
for i in xrange(len(sequence))
|
|
if i == 0 or sequence[i] != sequence[i-1]]
|
|
|
|
captured_color_sequence = _discard_delayed_frames(
|
|
captured_color_sequence)
|
|
expected_color_sequence = _discard_delayed_frames(
|
|
expected_color_sequence)
|
|
|
|
if (len(captured_color_sequence) > 0 and
|
|
captured_color_sequence[0] == self.INITIAL_BACKGROUND_COLOR):
|
|
captured_color_sequence.pop(0)
|
|
return captured_color_sequence == expected_color_sequence
|
|
|
|
def _test_screen_with_color_sequence(
|
|
self, test_mirrored, chameleon_port, error_list):
|
|
"""Tests the screen with the predefined color sequence.
|
|
|
|
@param test_mirrored: True to test mirrored mode. False not to.
|
|
@param chameleon_port: A general ChameleonPort object.
|
|
@param error_list: A list to append the error message to or None.
|
|
"""
|
|
self._open_color_sequence_tab(test_mirrored)
|
|
checksum_table = self._display_and_get_checksum_table(
|
|
chameleon_port, self.TEST_COLOR_SEQUENCE)
|
|
captured_checksums, timestamp_list = self._display_and_capture(
|
|
chameleon_port, self.TEST_COLOR_SEQUENCE)
|
|
self._display_facade.close_tab(self._test_tab_descriptor)
|
|
delay_time = [timestamp_list[i] - timestamp_list[i-1]
|
|
for i in xrange(1, len(timestamp_list))]
|
|
logging.info('Captured %d frames\n'
|
|
'Checksum_table: %s\n'
|
|
'Captured_checksums: %s\n'
|
|
'Timestamp_list: %s\n'
|
|
'Delay informtaion:\n'
|
|
'max = %r, min = %r, avg = %r\n',
|
|
len(captured_checksums), checksum_table,
|
|
captured_checksums, timestamp_list,
|
|
max(delay_time), min(delay_time),
|
|
sum(delay_time)/len(delay_time))
|
|
|
|
error = None
|
|
if self._tearing_test(
|
|
captured_checksums, checksum_table) is False:
|
|
error = 'Detected screen tearing'
|
|
else:
|
|
captured_color_sequence = [
|
|
checksum_table[checksum]
|
|
for checksum in captured_checksums]
|
|
if self._correction_test(
|
|
captured_color_sequence, self.TEST_COLOR_SEQUENCE) is False:
|
|
error = 'Detected missing, redundant or wrong frame(s)'
|
|
if error is not None and error_list is not None:
|
|
error_list.append(error)
|
|
|
|
def run_once(self, host, test_mirrored=False):
|
|
factory = remote_facade_factory.RemoteFacadeFactory(host)
|
|
self._display_facade = factory.create_display_facade()
|
|
self._test_tab_descriptor = None
|
|
chameleon_board = host.chameleon
|
|
|
|
chameleon_board.setup_and_reset(self.outputdir)
|
|
finder = chameleon_port_finder.ChameleonVideoInputFinder(
|
|
chameleon_board, self._display_facade)
|
|
|
|
errors = []
|
|
for chameleon_port in finder.iterate_all_ports():
|
|
|
|
logging.info('Set mirrored: %s', test_mirrored)
|
|
self._display_facade.set_mirrored(test_mirrored)
|
|
|
|
self._test_screen_with_color_sequence(
|
|
test_mirrored, chameleon_port, errors)
|
|
|
|
if errors:
|
|
raise error.TestFail('; '.join(set(errors)))
|