| # 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) |