blob: 473135c634946d41939089dd3c1de052748aad4e [file] [log] [blame]
# Copyright 2012 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test display functionality.
Description
-----------
This test checks basic display functionality by showing colors or images on
display, and asks the operator to judge if the output looks correct.
The test can also be used to show an image for ``idle_timeout`` seconds, and
automatically pass itself after timeout is reached.
Test Procedure
--------------
``Idle mode``
If ``idle_timeout`` is set and ``symptoms`` is not set:
1. An image is shown on the display.
2. If the image looks incorrect, press escape key to fail the test.
3. The test passes itself after ``idle_timeout`` seconds.
``Normal mode``
If ``idle_timeout`` and ``symptoms`` are both not set:
1. A table of images to be tested is shown.
2. Operator presses space key to show the image.
3. For each image, if it looks correct, operator presses enter key to mark the
item as passed, otherwise, operator presses escape key to mark the item as
failed. Operator can press space key to return to the table view.
4. The next image would be shown after the previous one is judged.
5. The test is passed if all items are judged as passed, and is failed if any
item is judged as failed.
``Symptom mode``
If ``symptom`` is set and ``idle_timeout`` is not set:
1. A table of images and symptoms to be checked is shown.
2. For each symptom, if it is observed, click the corresponding symptom grid
to mark it.
3. Press enter to end the subtest. The test is passed if all symptoms are not
marked, and is failed if any symptom is marked.
4. The next image would be shown after the previous one is judged.
5. The test is passed if all items are judged as passed, and is failed if any
item is judged as failed.
Dependency
----------
Each item of ``items`` is either:
1. ``image-<image_name>``: The corresponding <image_name> should be in the
compressed file ``test_images.tar.bz2``. For example, if the item is
"image-horizontal-rgbw.bmp", an image named "horizontal-rgbw.bmp"
should be in ``test_images.tar.bz2``. We support image file formats:
apng, avig, bmp, gif, ico, jpeg, png, svg, and webp.
2. ``hex-color-<hex_color_code>``.
3. A predefined CSS item listed in ``_CSS_ITEMS``.
Examples
--------
To test display functionality, add this into test list:
.. test_list::
generic_display_panel_examples:Display
To test display functionality, show gray image, idle for an hour and pass, add
this into test list:
.. test_list::
generic_display_panel_examples:DisplayGrayForAnHour
To test images with symptoms, add this into test list:
.. test_list::
generic_display_panel_examples:FrontOfScreenTestSymptom
To test display functionality, and show some more images, add this into test
list:
.. test_list::
generic_display_panel_examples:FrontOfScreenTestMoreImages
Default images in compressed file ``test_images.tar.bz2``::
['black.bmp',
'complex.bmp',
'crosstalk-black.bmp',
'crosstalk-white.bmp',
'gray-127.bmp',
'gray-170.bmp',
'gray-63.bmp',
'horizontal-rgbw.bmp',
'vertical-rgbw.bmp',
'white.bmp']
"""
import os
import re
from cros.factory.test.i18n import _
from cros.factory.test.i18n import translation
from cros.factory.test import test_case
from cros.factory.test import test_ui
from cros.factory.testlog import testlog
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import file_utils
# The _() is necessary for pygettext to get translatable strings correctly.
_CSS_ITEMS = [
_('solid-gray-170'),
_('solid-gray-127'),
_('solid-gray-63'),
_('solid-red'),
_('solid-green'),
_('solid-blue'),
_('solid-white'),
_('solid-gray'),
_('solid-black'),
_('grid'),
_('rectangle'),
_('gradient-red'),
_('gradient-green'),
_('gradient-blue'),
_('gradient-white')
]
_CSS_ITEMS = [x[translation.DEFAULT_LOCALE] for x in _CSS_ITEMS]
_IMAGE_PREFIX = 'image-'
_HEX_COLOR_PREFIX = 'hex-color-'
# This list is unused in this file. Just make sure the default images will be
# translated correctly.
_DEFAULT_IMAGES = [
_('image-complex'),
_('image-black'),
_('image-white'),
_('image-crosstalk-black'),
_('image-crosstalk-white'),
_('image-gray-63'),
_('image-gray-127'),
_('image-gray-170'),
_('image-horizontal-rgbw'),
_('image-vertical-rgbw')
]
class DisplayTest(test_case.TestCase):
"""Tests the function of display.
Properties:
ui: test ui.
checked: user has check the display of current subtest.
fullscreen: the test ui is in fullscreen or not.
static_dir: string of static file directory.
"""
related_components = (test_case.TestCategory.LCD, )
ARGS = [
Arg(
'items', list,
'Set items to be shown on screen. Available items are: items with '
'prefix "image-" and \n' +
'\n'.join(f' * ``"{x}"``' for x in _CSS_ITEMS) + '\n', default=[
'solid-gray-170', 'solid-gray-127', 'solid-gray-63', 'solid-red',
'solid-green', 'solid-blue'
]),
Arg(
'idle_timeout', int,
'If given, the test would be start automatically, run for '
'idle_timeout seconds, and pass itself. '
'Note that items should contain exactly one item in this mode.',
default=None),
Arg(
'quick_display', bool,
'If set to true, the next item will be shown automatically on '
'enter pressed i.e. no additional space needed to toggle screen.',
default=True),
Arg('show_timer', bool,
'If set to true, the timer will be shown in idle mode.',
default=False),
Arg('symptoms', list, 'Symptoms to check for images', default=[]),
]
def setUp(self):
"""Initializes frontend presentation and properties."""
# yapf: disable
self.static_dir = self.ui.GetStaticDirectoryPath() # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.idle_timeout = self.args.idle_timeout # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.items = self.args.items # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.symptoms = self.args.symptoms # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
if self.idle_timeout is not None:
if self.symptoms:
raise ValueError('Idle mode and symptom mode are incompatible.')
if len(self.items) != 1:
raise ValueError('items should have exactly one item in idle mode.')
self.images = [
item[len(_IMAGE_PREFIX):]
for item in self.items
if item.startswith(_IMAGE_PREFIX)
]
if self.images:
self.ExtractTestImages()
invalid_hex_color_items = [
item for item in self.items if item.startswith(_HEX_COLOR_PREFIX) and
not self._IsHexColor(item[len(_HEX_COLOR_PREFIX):])
]
if invalid_hex_color_items:
raise ValueError(f'{invalid_hex_color_items} are not valid hex colors.')
unknown_items = [
item for item in set(self.items) - set(_CSS_ITEMS)
if not item.startswith(_IMAGE_PREFIX) and
not item.startswith(_HEX_COLOR_PREFIX)
]
if unknown_items:
raise ValueError(f'Unknown item {unknown_items!r} in items.')
# yapf: disable
self.frontend_proxy = self.ui.InitJSTestObject('DisplayTest', self.items, # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.symptoms)
self.checked = False
self.fullscreen = False
def tearDown(self):
self.RemoveTestImages()
def runTest(self):
"""Sets the callback function of keys."""
# yapf: disable
self.event_loop.AddEventHandler('failed_lists', # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.LogFailedListsAndFinishTask)
# yapf: disable
self.event_loop.AddEventHandler('pass_subtest', self.OnEnterPressed) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
if self.idle_timeout is None:
# yapf: disable
self.ui.BindKey(test_ui.SPACE_KEY, self.OnSpacePressed) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.ui.BindKey(test_ui.ENTER_KEY, self.OnEnterPressed) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
if not self.symptoms:
# Fail the subtest with Escape key in Normal mode.
# yapf: disable
self.ui.BindKey(test_ui.ESCAPE_KEY, self.OnFailPressed) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
else:
# Idle mode
# Automatically enter fullscreen mode in idle mode.
self.ToggleFullscreen()
# yapf: disable
self.ui.BindKey(test_ui.ESCAPE_KEY, self.OnFailPressed) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.ui.StartCountdownTimer(self.idle_timeout, self.PassTask) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
if self.args.show_timer: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.ui.ShowElement('display-timer') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.WaitTaskEnd()
def ExtractTestImages(self):
"""Extracts selected test images from test_images.tar.bz2."""
file_utils.ExtractFile(os.path.join(self.static_dir, 'test_images.tar.bz2'),
self.static_dir, only_extracts=self.images)
def RemoveTestImages(self):
"""Removes extracted image files after test finished."""
for image in self.images:
file_utils.TryUnlink(os.path.join(self.static_dir, image))
def OnSpacePressed(self, event):
"""Sets self.checked to True. Calls JS function to switch display on/off."""
del event # Unused.
self.ToggleFullscreen()
def OnEnterPressed(self, event):
"""Passes the subtest only if self.checked is True."""
del event # Unused.
if self.checked:
if not self.symptoms:
self.frontend_proxy.JudgeSubTest(True)
else:
self.frontend_proxy.JudgeSubTestWithSymptom()
# If the next subtest will be in fullscreen mode, checked should be True
self.checked = self.fullscreen
# yapf: disable
if self.args.quick_display and not self.fullscreen: # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.ToggleFullscreen()
def OnFailPressed(self, event):
"""Fails the subtest only if self.checked is True."""
del event # Unused.
if self.checked:
self.frontend_proxy.JudgeSubTest(False)
# If the next subtest will be in fullscreen mode, checked should be True
self.checked = self.fullscreen
def LogFailedListsAndFinishTask(self, event):
failed_items = event.data[0]
failed_symptoms = event.data[1]
if failed_symptoms:
failed_item_symptom = [
f'{failed_items[i]}: {", ".join(failed_symptoms[i])}'
for i in range(len(failed_items))
]
testlog.LogParam('display_test_failed_list', failed_item_symptom)
self.FailTask(
f'Display test failed. Failed items and the corresponding symptoms:'
f'{failed_item_symptom}')
if failed_items:
testlog.LogParam('display_test_failed_list', failed_items)
self.FailTask(f'Display test failed. Failed items: {failed_items}')
self.PassTask()
def ToggleFullscreen(self):
self.checked = True
self.frontend_proxy.ToggleFullscreen()
self.fullscreen = not self.fullscreen
@classmethod
def _IsHexColor(cls, color_code):
pattern = r'#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})'
return re.fullmatch(pattern, color_code)