# 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_index())
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.reset()
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)))