Home | History | Annotate | Download | only in graphics
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """
      6 Provides graphics related utils, like capturing screenshots or checking on
      7 the state of the graphics driver.
      8 """
      9 
     10 import collections
     11 import contextlib
     12 import fcntl
     13 import glob
     14 import logging
     15 import os
     16 import re
     17 import struct
     18 import sys
     19 import time
     20 #import traceback
     21 # Please limit the use of the uinput library to this file. Try not to spread
     22 # dependencies and abstract as much as possible to make switching to a different
     23 # input library in the future easier.
     24 import uinput
     25 
     26 from autotest_lib.client.bin import test
     27 from autotest_lib.client.bin import utils
     28 from autotest_lib.client.common_lib import error
     29 from autotest_lib.client.common_lib import test as test_utils
     30 from autotest_lib.client.cros.input_playback import input_playback
     31 from autotest_lib.client.cros.power import power_utils
     32 from functools import wraps
     33 
     34 
     35 class GraphicsTest(test.test):
     36     """Base class for graphics test.
     37 
     38     GraphicsTest is the base class for graphics tests.
     39     Every subclass of GraphicsTest should call GraphicsTests initialize/cleanup
     40     method as they will do GraphicsStateChecker as well as report states to
     41     Chrome Perf dashboard.
     42 
     43     Attributes:
     44         _test_failure_description(str): Failure name reported to chrome perf
     45                                         dashboard. (Default: Failures)
     46         _test_failure_report_enable(bool): Enable/Disable reporting
     47                                             failures to chrome perf dashboard
     48                                             automatically. (Default: True)
     49         _test_failure_report_subtest(bool): Enable/Disable reporting
     50                                             subtests failure to chrome perf
     51                                             dashboard automatically.
     52                                             (Default: False)
     53     """
     54     version = 1
     55     _GSC = None
     56 
     57     _test_failure_description = "Failures"
     58     _test_failure_report_enable = True
     59     _test_failure_report_subtest = False
     60 
     61     def __init__(self, *args, **kwargs):
     62         """Initialize flag setting."""
     63         super(GraphicsTest, self).__init__(*args, **kwargs)
     64         self._failures_by_description = {}
     65         self._player = None
     66 
     67     def initialize(self, raise_error_on_hang=False, *args, **kwargs):
     68         """Initial state checker and report initial value to perf dashboard."""
     69         self._GSC = GraphicsStateChecker(
     70             raise_error_on_hang=raise_error_on_hang,
     71             run_on_sw_rasterizer=utils.is_virtual_machine())
     72 
     73         self.output_perf_value(
     74             description='Timeout_Reboot',
     75             value=1,
     76             units='count',
     77             higher_is_better=False,
     78             replace_existing_values=True
     79         )
     80 
     81         # Enable the graphics tests to use keyboard interaction.
     82         self._player = input_playback.InputPlayback()
     83         self._player.emulate(input_type='keyboard')
     84         self._player.find_connected_inputs()
     85 
     86         if hasattr(super(GraphicsTest, self), "initialize"):
     87             test_utils._cherry_pick_call(super(GraphicsTest, self).initialize,
     88                                          *args, **kwargs)
     89 
     90     def cleanup(self, *args, **kwargs):
     91         """Finalize state checker and report values to perf dashboard."""
     92         if self._GSC:
     93             self._GSC.finalize()
     94 
     95         self._output_perf()
     96         if self._player:
     97             self._player.close()
     98 
     99         if hasattr(super(GraphicsTest, self), "cleanup"):
    100             test_utils._cherry_pick_call(super(GraphicsTest, self).cleanup,
    101                                          *args, **kwargs)
    102 
    103     @contextlib.contextmanager
    104     def failure_report(self, name, subtest=None):
    105         """Record the failure of an operation to self._failures_by_description.
    106 
    107         Records if the operation taken inside executed normally or not.
    108         If the operation taken inside raise unexpected failure, failure named
    109         |name|, will be added to the self._failures_by_description dictionary
    110         and reported to the chrome perf dashboard in the cleanup stage.
    111 
    112         Usage:
    113             # Record failure of doSomething
    114             with failure_report('doSomething'):
    115                 doSomething()
    116         """
    117         # Assume failed at the beginning
    118         self.add_failures(name, subtest=subtest)
    119         yield {}
    120         self.remove_failures(name, subtest=subtest)
    121 
    122     @classmethod
    123     def failure_report_decorator(cls, name, subtest=None):
    124         """Record the failure if the function failed to finish.
    125         This method should only decorate to functions of GraphicsTest.
    126         In addition, functions with this decorator should be called with no
    127         unnamed arguments.
    128         Usage:
    129             @GraphicsTest.test_run_decorator('graphics_test')
    130             def Foo(self, bar='test'):
    131                 return doStuff()
    132 
    133             is equivalent to
    134 
    135             def Foo(self, bar):
    136                 with failure_reporter('graphics_test'):
    137                     return doStuff()
    138 
    139             # Incorrect usage.
    140             @GraphicsTest.test_run_decorator('graphics_test')
    141             def Foo(self, bar='test'):
    142                 pass
    143             self.Foo('test_name', bar='test_name') # call Foo with named args
    144 
    145             # Incorrect usage.
    146             @GraphicsTest.test_run_decorator('graphics_test')
    147             def Foo(self, bar='test'):
    148                 pass
    149             self.Foo('test_name') # call Foo with unnamed args
    150          """
    151         def decorator(fn):
    152             @wraps(fn)
    153             def wrapper(*args, **kwargs):
    154                 if len(args) > 1:
    155                     raise error.TestError('Unnamed arguments is not accepted. '
    156                                           'Please apply this decorator to '
    157                                           'function without unnamed args.')
    158                 # A member function of GraphicsTest is decorated. The first
    159                 # argument is the instance itself.
    160                 instance = args[0]
    161                 with instance.failure_report(name, subtest):
    162                     # Cherry pick the arguments for the wrapped function.
    163                     d_args, d_kwargs = test_utils._cherry_pick_args(fn, args,
    164                                                                     kwargs)
    165                     return fn(instance, *d_args, **d_kwargs)
    166             return wrapper
    167         return decorator
    168 
    169     def add_failures(self, name, subtest=None):
    170         """
    171         Add a record to failures list which will report back to chrome perf
    172         dashboard at cleanup stage.
    173         Args:
    174             name: failure name.
    175             subtest: subtest which will appears in cros-perf. If None is
    176                      specified, use name instead.
    177         """
    178         target = self._get_failure(name, subtest=subtest)
    179         if target:
    180             target['names'].append(name)
    181         else:
    182             target = {
    183                 'description': self._get_failure_description(name, subtest),
    184                 'unit': 'count',
    185                 'higher_is_better': False,
    186                 'graph': self._get_failure_graph_name(),
    187                 'names': [name],
    188             }
    189             self._failures_by_description[target['description']] = target
    190         return target
    191 
    192     def remove_failures(self, name, subtest=None):
    193         """
    194         Remove a record from failures list which will report back to chrome perf
    195         dashboard at cleanup stage.
    196         Args:
    197             name: failure name.
    198             subtest: subtest which will appears in cros-perf. If None is
    199                      specified, use name instead.
    200         """
    201         target = self._get_failure(name, subtest=subtest)
    202         if name in target['names']:
    203             target['names'].remove(name)
    204 
    205 
    206     def _output_perf(self):
    207         """Report recorded failures back to chrome perf."""
    208         self.output_perf_value(
    209             description='Timeout_Reboot',
    210             value=0,
    211             units='count',
    212             higher_is_better=False,
    213             replace_existing_values=True
    214         )
    215 
    216         if not self._test_failure_report_enable:
    217             return
    218 
    219         total_failures = 0
    220         # Report subtests failures
    221         for failure in self._failures_by_description.values():
    222             if len(failure['names']) > 0:
    223                 logging.debug('GraphicsTest failure: %s' % failure['names'])
    224                 total_failures += len(failure['names'])
    225 
    226             if not self._test_failure_report_subtest:
    227                 continue
    228 
    229             self.output_perf_value(
    230                 description=failure['description'],
    231                 value=len(failure['names']),
    232                 units=failure['unit'],
    233                 higher_is_better=failure['higher_is_better'],
    234                 graph=failure['graph']
    235             )
    236 
    237         # Report the count of all failures
    238         self.output_perf_value(
    239             description=self._get_failure_graph_name(),
    240             value=total_failures,
    241             units='count',
    242             higher_is_better=False,
    243         )
    244 
    245     def _get_failure_graph_name(self):
    246         return self._test_failure_description
    247 
    248     def _get_failure_description(self, name, subtest):
    249         return subtest or name
    250 
    251     def _get_failure(self, name, subtest):
    252         """Get specific failures."""
    253         description = self._get_failure_description(name, subtest=subtest)
    254         return self._failures_by_description.get(description, None)
    255 
    256     def get_failures(self):
    257         """
    258         Get currently recorded failures list.
    259         """
    260         return [name for failure in self._failures_by_description.values()
    261                 for name in failure['names']]
    262 
    263     def open_vt1(self):
    264         """Switch to VT1 with keyboard."""
    265         self._player.blocking_playback_of_default_file(
    266             input_type='keyboard', filename='keyboard_ctrl+alt+f1')
    267         time.sleep(5)
    268 
    269     def open_vt2(self):
    270         """Switch to VT2 with keyboard."""
    271         self._player.blocking_playback_of_default_file(
    272             input_type='keyboard', filename='keyboard_ctrl+alt+f2')
    273         time.sleep(5)
    274 
    275     def wake_screen_with_keyboard(self):
    276         """Use the vt1 keyboard shortcut to bring the devices screen back on.
    277 
    278         This is useful if you want to take screenshots of the UI. If you try
    279         to take them while the screen is off, it will fail.
    280         """
    281         self.open_vt1()
    282 
    283 
    284 def screen_disable_blanking():
    285     """ Called from power_Backlight to disable screen blanking. """
    286     # We don't have to worry about unexpected screensavers or DPMS here.
    287     return
    288 
    289 
    290 def screen_disable_energy_saving():
    291     """ Called from power_Consumption to immediately disable energy saving. """
    292     # All we need to do here is enable displays via Chrome.
    293     power_utils.set_display_power(power_utils.DISPLAY_POWER_ALL_ON)
    294     return
    295 
    296 
    297 def screen_toggle_fullscreen():
    298     """Toggles fullscreen mode."""
    299     press_keys(['KEY_F11'])
    300 
    301 
    302 def screen_toggle_mirrored():
    303     """Toggles the mirrored screen."""
    304     press_keys(['KEY_LEFTCTRL', 'KEY_F4'])
    305 
    306 
    307 def hide_cursor():
    308     """Hides mouse cursor."""
    309     # Send a keystroke to hide the cursor.
    310     press_keys(['KEY_UP'])
    311 
    312 
    313 def hide_typing_cursor():
    314     """Hides typing cursor."""
    315     # Press the tab key to move outside the typing bar.
    316     press_keys(['KEY_TAB'])
    317 
    318 
    319 def screen_wakeup():
    320     """Wake up the screen if it is dark."""
    321     # Move the mouse a little bit to wake up the screen.
    322     device = _get_uinput_device_mouse_rel()
    323     _uinput_emit(device, 'REL_X', 1)
    324     _uinput_emit(device, 'REL_X', -1)
    325 
    326 
    327 def switch_screen_on(on):
    328     """
    329     Turn the touch screen on/off.
    330 
    331     @param on: On or off.
    332     """
    333     raise error.TestFail('switch_screen_on is not implemented.')
    334 
    335 
    336 # Don't create a device during build_packages or for tests that don't need it.
    337 uinput_device_keyboard = None
    338 uinput_device_touch = None
    339 uinput_device_mouse_rel = None
    340 
    341 # Don't add more events to this list than are used. For a complete list of
    342 # available events check python2.7/site-packages/uinput/ev.py.
    343 UINPUT_DEVICE_EVENTS_KEYBOARD = [
    344     uinput.KEY_F4,
    345     uinput.KEY_F11,
    346     uinput.KEY_KPPLUS,
    347     uinput.KEY_KPMINUS,
    348     uinput.KEY_LEFTCTRL,
    349     uinput.KEY_TAB,
    350     uinput.KEY_UP,
    351     uinput.KEY_DOWN,
    352     uinput.KEY_LEFT,
    353     uinput.KEY_RIGHT,
    354     uinput.KEY_RIGHTSHIFT,
    355     uinput.KEY_LEFTALT,
    356     uinput.KEY_A,
    357     uinput.KEY_M,
    358     uinput.KEY_Q,
    359     uinput.KEY_V
    360 ]
    361 # TODO(ihf): Find an ABS sequence that actually works.
    362 UINPUT_DEVICE_EVENTS_TOUCH = [
    363     uinput.BTN_TOUCH,
    364     uinput.ABS_MT_SLOT,
    365     uinput.ABS_MT_POSITION_X + (0, 2560, 0, 0),
    366     uinput.ABS_MT_POSITION_Y + (0, 1700, 0, 0),
    367     uinput.ABS_MT_TRACKING_ID + (0, 10, 0, 0),
    368     uinput.BTN_TOUCH
    369 ]
    370 UINPUT_DEVICE_EVENTS_MOUSE_REL = [
    371     uinput.REL_X,
    372     uinput.REL_Y,
    373     uinput.BTN_MOUSE,
    374     uinput.BTN_LEFT,
    375     uinput.BTN_RIGHT
    376 ]
    377 
    378 
    379 def _get_uinput_device_keyboard():
    380     """
    381     Lazy initialize device and return it. We don't want to create a device
    382     during build_packages or for tests that don't need it, hence init with None.
    383     """
    384     global uinput_device_keyboard
    385     if uinput_device_keyboard is None:
    386         uinput_device_keyboard = uinput.Device(UINPUT_DEVICE_EVENTS_KEYBOARD)
    387     return uinput_device_keyboard
    388 
    389 
    390 def _get_uinput_device_mouse_rel():
    391     """
    392     Lazy initialize device and return it. We don't want to create a device
    393     during build_packages or for tests that don't need it, hence init with None.
    394     """
    395     global uinput_device_mouse_rel
    396     if uinput_device_mouse_rel is None:
    397         uinput_device_mouse_rel = uinput.Device(UINPUT_DEVICE_EVENTS_MOUSE_REL)
    398     return uinput_device_mouse_rel
    399 
    400 
    401 def _get_uinput_device_touch():
    402     """
    403     Lazy initialize device and return it. We don't want to create a device
    404     during build_packages or for tests that don't need it, hence init with None.
    405     """
    406     global uinput_device_touch
    407     if uinput_device_touch is None:
    408         uinput_device_touch = uinput.Device(UINPUT_DEVICE_EVENTS_TOUCH)
    409     return uinput_device_touch
    410 
    411 
    412 def _uinput_translate_name(event_name):
    413     """
    414     Translates string |event_name| to uinput event.
    415     """
    416     return getattr(uinput, event_name)
    417 
    418 
    419 def _uinput_emit(device, event_name, value, syn=True):
    420     """
    421     Wrapper for uinput.emit. Emits event with value.
    422     Example: ('REL_X', 20), ('BTN_RIGHT', 1)
    423     """
    424     event = _uinput_translate_name(event_name)
    425     device.emit(event, value, syn)
    426 
    427 
    428 def _uinput_emit_click(device, event_name, syn=True):
    429     """
    430     Wrapper for uinput.emit_click. Emits click event. Only KEY and BTN events
    431     are accepted, otherwise ValueError is raised. Example: 'KEY_A'
    432     """
    433     event = _uinput_translate_name(event_name)
    434     device.emit_click(event, syn)
    435 
    436 
    437 def _uinput_emit_combo(device, event_names, syn=True):
    438     """
    439     Wrapper for uinput.emit_combo. Emits sequence of events.
    440     Example: ['KEY_LEFTCTRL', 'KEY_LEFTALT', 'KEY_F5']
    441     """
    442     events = [_uinput_translate_name(en) for en in event_names]
    443     device.emit_combo(events, syn)
    444 
    445 
    446 def press_keys(key_list):
    447     """Presses the given keys as one combination.
    448 
    449     Please do not leak uinput dependencies outside of the file.
    450 
    451     @param key: A list of key strings, e.g. ['LEFTCTRL', 'F4']
    452     """
    453     _uinput_emit_combo(_get_uinput_device_keyboard(), key_list)
    454 
    455 
    456 def click_mouse():
    457     """Just click the mouse.
    458     Presumably only hacky tests use this function.
    459     """
    460     logging.info('click_mouse()')
    461     # Move a little to make the cursor appear.
    462     device = _get_uinput_device_mouse_rel()
    463     _uinput_emit(device, 'REL_X', 1)
    464     # Some sleeping is needed otherwise events disappear.
    465     time.sleep(0.1)
    466     # Move cursor back to not drift.
    467     _uinput_emit(device, 'REL_X', -1)
    468     time.sleep(0.1)
    469     # Click down.
    470     _uinput_emit(device, 'BTN_LEFT', 1)
    471     time.sleep(0.2)
    472     # Release click.
    473     _uinput_emit(device, 'BTN_LEFT', 0)
    474 
    475 
    476 # TODO(ihf): this function is broken. Make it work.
    477 def activate_focus_at(rel_x, rel_y):
    478     """Clicks with the mouse at screen position (x, y).
    479 
    480     This is a pretty hacky method. Using this will probably lead to
    481     flaky tests as page layout changes over time.
    482     @param rel_x: relative horizontal position between 0 and 1.
    483     @param rel_y: relattive vertical position between 0 and 1.
    484     """
    485     width, height = get_internal_resolution()
    486     device = _get_uinput_device_touch()
    487     _uinput_emit(device, 'ABS_MT_SLOT', 0, syn=False)
    488     _uinput_emit(device, 'ABS_MT_TRACKING_ID', 1, syn=False)
    489     _uinput_emit(device, 'ABS_MT_POSITION_X', int(rel_x * width), syn=False)
    490     _uinput_emit(device, 'ABS_MT_POSITION_Y', int(rel_y * height), syn=False)
    491     _uinput_emit(device, 'BTN_TOUCH', 1, syn=True)
    492     time.sleep(0.2)
    493     _uinput_emit(device, 'BTN_TOUCH', 0, syn=True)
    494 
    495 
    496 def take_screenshot(resultsdir, fname_prefix):
    497     """Take screenshot and save to a new file in the results dir.
    498     Args:
    499       @param resultsdir:   Directory to store the output in.
    500       @param fname_prefix: Prefix for the output fname.
    501     Returns:
    502       the path of the saved screenshot file
    503     """
    504 
    505     old_exc_type = sys.exc_info()[0]
    506 
    507     next_index = len(glob.glob(
    508         os.path.join(resultsdir, '%s-*.png' % fname_prefix)))
    509     screenshot_file = os.path.join(
    510         resultsdir, '%s-%d.png' % (fname_prefix, next_index))
    511     logging.info('Saving screenshot to %s.', screenshot_file)
    512 
    513     try:
    514         utils.run('screenshot "%s"' % screenshot_file)
    515     except Exception as err:
    516         # Do not raise an exception if the screenshot fails while processing
    517         # another exception.
    518         if old_exc_type is None:
    519             raise
    520         logging.error(err)
    521 
    522     return screenshot_file
    523 
    524 
    525 def take_screenshot_crop(fullpath, box=None, crtc_id=None):
    526     """
    527     Take a screenshot using import tool, crop according to dim given by the box.
    528     @param fullpath: path, full path to save the image to.
    529     @param box: 4-tuple giving the upper left and lower right pixel coordinates.
    530     @param crtc_id: if set, take a screen shot of the specified CRTC.
    531     """
    532     cmd = 'screenshot'
    533     if crtc_id is not None:
    534         cmd += ' --crtc-id=%d' % crtc_id
    535     else:
    536         cmd += ' --internal'
    537     if box:
    538         x, y, r, b = box
    539         w = r - x
    540         h = b - y
    541         cmd += ' --crop=%dx%d+%d+%d' % (w, h, x, y)
    542     cmd += ' "%s"' % fullpath
    543     utils.run(cmd)
    544     return fullpath
    545 
    546 
    547 _MODETEST_CONNECTOR_PATTERN = re.compile(
    548     r'^(\d+)\s+\d+\s+(connected|disconnected)\s+(\S+)\s+\d+x\d+\s+\d+\s+\d+')
    549 
    550 _MODETEST_MODE_PATTERN = re.compile(
    551     r'\s+.+\d+\s+(\d+)\s+\d+\s+\d+\s+\d+\s+(\d+)\s+\d+\s+\d+\s+\d+\s+flags:.+type:'
    552     r' preferred')
    553 
    554 _MODETEST_CRTCS_START_PATTERN = re.compile(r'^id\s+fb\s+pos\s+size')
    555 
    556 _MODETEST_CRTC_PATTERN = re.compile(
    557     r'^(\d+)\s+(\d+)\s+\((\d+),(\d+)\)\s+\((\d+)x(\d+)\)')
    558 
    559 _MODETEST_PLANES_START_PATTERN = re.compile(
    560     r'^id\s+crtc\s+fb\s+CRTC\s+x,y\s+x,y\s+gamma\s+size\s+possible\s+crtcs')
    561 
    562 _MODETEST_PLANE_PATTERN = re.compile(
    563     r'^(\d+)\s+(\d+)\s+(\d+)\s+(\d+),(\d+)\s+(\d+),(\d+)\s+(\d+)\s+(0x)(\d+)')
    564 
    565 Connector = collections.namedtuple(
    566     'Connector', [
    567         'cid',  # connector id (integer)
    568         'ctype',  # connector type, e.g. 'eDP', 'HDMI-A', 'DP'
    569         'connected',  # boolean
    570         'size',  # current screen size, e.g. (1024, 768)
    571         'encoder',  # encoder id (integer)
    572         # list of resolution tuples, e.g. [(1920,1080), (1600,900), ...]
    573         'modes',
    574     ])
    575 
    576 CRTC = collections.namedtuple(
    577     'CRTC', [
    578         'id',  # crtc id
    579         'fb',  # fb id
    580         'pos',  # position, e.g. (0,0)
    581         'size',  # size, e.g. (1366,768)
    582     ])
    583 
    584 Plane = collections.namedtuple(
    585     'Plane', [
    586         'id',  # plane id
    587         'possible_crtcs',  # possible associated CRTC indexes.
    588     ])
    589 
    590 def get_display_resolution():
    591     """
    592     Parses output of modetest to determine the display resolution of the dut.
    593     @return: tuple, (w,h) resolution of device under test.
    594     """
    595     connectors = get_modetest_connectors()
    596     for connector in connectors:
    597         if connector.connected:
    598             return connector.size
    599     return None
    600 
    601 
    602 def _get_num_outputs_connected():
    603     """
    604     Parses output of modetest to determine the number of connected displays
    605     @return: The number of connected displays
    606     """
    607     connected = 0
    608     connectors = get_modetest_connectors()
    609     for connector in connectors:
    610         if connector.connected:
    611             connected = connected + 1
    612 
    613     return connected
    614 
    615 
    616 def get_num_outputs_on():
    617     """
    618     Retrieves the number of connected outputs that are on.
    619 
    620     Return value: integer value of number of connected outputs that are on.
    621     """
    622 
    623     return _get_num_outputs_connected()
    624 
    625 
    626 def get_modetest_connectors():
    627     """
    628     Retrieves a list of Connectors using modetest.
    629 
    630     Return value: List of Connectors.
    631     """
    632     connectors = []
    633     modetest_output = utils.system_output('modetest -c')
    634     for line in modetest_output.splitlines():
    635         # First search for a new connector.
    636         connector_match = re.match(_MODETEST_CONNECTOR_PATTERN, line)
    637         if connector_match is not None:
    638             cid = int(connector_match.group(1))
    639             connected = False
    640             if connector_match.group(2) == 'connected':
    641                 connected = True
    642             ctype = connector_match.group(3)
    643             size = (-1, -1)
    644             encoder = -1
    645             modes = None
    646             connectors.append(
    647                 Connector(cid, ctype, connected, size, encoder, modes))
    648         else:
    649             # See if we find corresponding line with modes, sizes etc.
    650             mode_match = re.match(_MODETEST_MODE_PATTERN, line)
    651             if mode_match is not None:
    652                 size = (int(mode_match.group(1)), int(mode_match.group(2)))
    653                 # Update display size of last connector in list.
    654                 c = connectors.pop()
    655                 connectors.append(
    656                     Connector(
    657                         c.cid, c.ctype, c.connected, size, c.encoder,
    658                         c.modes))
    659     return connectors
    660 
    661 
    662 def get_modetest_crtcs():
    663     """
    664     Returns a list of CRTC data.
    665 
    666     Sample:
    667         [CRTC(id=19, fb=50, pos=(0, 0), size=(1366, 768)),
    668          CRTC(id=22, fb=54, pos=(0, 0), size=(1920, 1080))]
    669     """
    670     crtcs = []
    671     modetest_output = utils.system_output('modetest -p')
    672     found = False
    673     for line in modetest_output.splitlines():
    674         if found:
    675             crtc_match = re.match(_MODETEST_CRTC_PATTERN, line)
    676             if crtc_match is not None:
    677                 crtc_id = int(crtc_match.group(1))
    678                 fb = int(crtc_match.group(2))
    679                 x = int(crtc_match.group(3))
    680                 y = int(crtc_match.group(4))
    681                 width = int(crtc_match.group(5))
    682                 height = int(crtc_match.group(6))
    683                 # CRTCs with fb=0 are disabled, but lets skip anything with
    684                 # trivial width/height just in case.
    685                 if not (fb == 0 or width == 0 or height == 0):
    686                     crtcs.append(CRTC(crtc_id, fb, (x, y), (width, height)))
    687             elif line and not line[0].isspace():
    688                 return crtcs
    689         if re.match(_MODETEST_CRTCS_START_PATTERN, line) is not None:
    690             found = True
    691     return crtcs
    692 
    693 
    694 def get_modetest_planes():
    695     """
    696     Returns a list of planes information.
    697 
    698     Sample:
    699         [Plane(id=26, possible_crtcs=1),
    700          Plane(id=29, possible_crtcs=1)]
    701     """
    702     planes = []
    703     modetest_output = utils.system_output('modetest -p')
    704     found = False
    705     for line in modetest_output.splitlines():
    706         if found:
    707             plane_match = re.match(_MODETEST_PLANE_PATTERN, line)
    708             if plane_match is not None:
    709                 plane_id = int(plane_match.group(1))
    710                 possible_crtcs = int(plane_match.group(10))
    711                 if not (plane_id == 0 or possible_crtcs == 0):
    712                     planes.append(Plane(plane_id, possible_crtcs))
    713             elif line and not line[0].isspace():
    714                 return planes
    715         if re.match(_MODETEST_PLANES_START_PATTERN, line) is not None:
    716             found = True
    717     return planes
    718 
    719 
    720 def get_modetest_output_state():
    721     """
    722     Reduce the output of get_modetest_connectors to a dictionary of connector/active states.
    723     """
    724     connectors = get_modetest_connectors()
    725     outputs = {}
    726     for connector in connectors:
    727         # TODO(ihf): Figure out why modetest output needs filtering.
    728         if connector.connected:
    729             outputs[connector.ctype] = connector.connected
    730     return outputs
    731 
    732 
    733 def get_output_rect(output):
    734     """Gets the size and position of the given output on the screen buffer.
    735 
    736     @param output: The output name as a string.
    737 
    738     @return A tuple of the rectangle (width, height, fb_offset_x,
    739             fb_offset_y) of ints.
    740     """
    741     connectors = get_modetest_connectors()
    742     for connector in connectors:
    743         if connector.ctype == output:
    744             # Concatenate two 2-tuples to 4-tuple.
    745             return connector.size + (0, 0)  # TODO(ihf): Should we use CRTC.pos?
    746     return (0, 0, 0, 0)
    747 
    748 
    749 def get_internal_resolution():
    750     if has_internal_display():
    751         crtcs = get_modetest_crtcs()
    752         if len(crtcs) > 0:
    753             return crtcs[0].size
    754     return (-1, -1)
    755 
    756 
    757 def has_internal_display():
    758     """Checks whether the DUT is equipped with an internal display.
    759 
    760     @return True if internal display is present; False otherwise.
    761     """
    762     return bool(get_internal_connector_name())
    763 
    764 
    765 def get_external_resolution():
    766     """Gets the resolution of the external display.
    767 
    768     @return A tuple of (width, height) or None if no external display is
    769             connected.
    770     """
    771     offset = 1 if has_internal_display() else 0
    772     crtcs = get_modetest_crtcs()
    773     if len(crtcs) > offset and crtcs[offset].size != (0, 0):
    774         return crtcs[offset].size
    775     return None
    776 
    777 
    778 def get_display_output_state():
    779     """
    780     Retrieves output status of connected display(s).
    781 
    782     Return value: dictionary of connected display states.
    783     """
    784     return get_modetest_output_state()
    785 
    786 
    787 def set_modetest_output(output_name, enable):
    788     # TODO(ihf): figure out what to do here. Don't think this is the right command.
    789     # modetest -s <connector_id>[,<connector_id>][@<crtc_id>]:<mode>[-<vrefresh>][@<format>]  set a mode
    790     pass
    791 
    792 
    793 def set_display_output(output_name, enable):
    794     """
    795     Sets the output given by |output_name| on or off.
    796     """
    797     set_modetest_output(output_name, enable)
    798 
    799 
    800 # TODO(ihf): Fix this for multiple external connectors.
    801 def get_external_crtc(index=0):
    802     offset = 1 if has_internal_display() else 0
    803     crtcs = get_modetest_crtcs()
    804     if len(crtcs) > offset + index:
    805         return crtcs[offset + index].id
    806     return -1
    807 
    808 
    809 def get_internal_crtc():
    810     if has_internal_display():
    811         crtcs = get_modetest_crtcs()
    812         if len(crtcs) > 0:
    813             return crtcs[0].id
    814     return -1
    815 
    816 
    817 # TODO(ihf): Fix this for multiple external connectors.
    818 def get_external_connector_name():
    819     """Gets the name of the external output connector.
    820 
    821     @return The external output connector name as a string, if any.
    822             Otherwise, return False.
    823     """
    824     outputs = get_display_output_state()
    825     for output in outputs.iterkeys():
    826         if outputs[output] and (output.startswith('HDMI')
    827                 or output.startswith('DP')
    828                 or output.startswith('DVI')
    829                 or output.startswith('VGA')):
    830             return output
    831     return False
    832 
    833 
    834 def get_internal_connector_name():
    835     """Gets the name of the internal output connector.
    836 
    837     @return The internal output connector name as a string, if any.
    838             Otherwise, return False.
    839     """
    840     outputs = get_display_output_state()
    841     for output in outputs.iterkeys():
    842         # reference: chromium_org/chromeos/display/output_util.cc
    843         if (output.startswith('eDP')
    844                 or output.startswith('LVDS')
    845                 or output.startswith('DSI')):
    846             return output
    847     return False
    848 
    849 
    850 def wait_output_connected(output):
    851     """Wait for output to connect.
    852 
    853     @param output: The output name as a string.
    854 
    855     @return: True if output is connected; False otherwise.
    856     """
    857     def _is_connected(output):
    858         """Helper function."""
    859         outputs = get_display_output_state()
    860         if output not in outputs:
    861             return False
    862         return outputs[output]
    863 
    864     return utils.wait_for_value(lambda: _is_connected(output),
    865                                 expected_value=True)
    866 
    867 
    868 def set_content_protection(output_name, state):
    869     """
    870     Sets the content protection to the given state.
    871 
    872     @param output_name: The output name as a string.
    873     @param state: One of the states 'Undesired', 'Desired', or 'Enabled'
    874 
    875     """
    876     raise error.TestFail('freon: set_content_protection not implemented')
    877 
    878 
    879 def get_content_protection(output_name):
    880     """
    881     Gets the state of the content protection.
    882 
    883     @param output_name: The output name as a string.
    884     @return: A string of the state, like 'Undesired', 'Desired', or 'Enabled'.
    885              False if not supported.
    886 
    887     """
    888     raise error.TestFail('freon: get_content_protection not implemented')
    889 
    890 
    891 def is_sw_rasterizer():
    892     """Return true if OpenGL is using a software rendering."""
    893     cmd = utils.wflinfo_cmd() + ' | grep "OpenGL renderer string"'
    894     output = utils.run(cmd)
    895     result = output.stdout.splitlines()[0]
    896     logging.info('wflinfo: %s', result)
    897     # TODO(ihf): Find exhaustive error conditions (especially ARM).
    898     return 'llvmpipe' in result.lower() or 'soft' in result.lower()
    899 
    900 
    901 def get_gles_version():
    902     cmd = utils.wflinfo_cmd()
    903     wflinfo = utils.system_output(cmd, retain_output=False, ignore_status=False)
    904     # OpenGL version string: OpenGL ES 3.0 Mesa 10.5.0-devel
    905     version = re.findall(r'OpenGL version string: '
    906                          r'OpenGL ES ([0-9]+).([0-9]+)', wflinfo)
    907     if version:
    908         version_major = int(version[0][0])
    909         version_minor = int(version[0][1])
    910         return (version_major, version_minor)
    911     return (None, None)
    912 
    913 
    914 def get_egl_version():
    915     cmd = 'eglinfo'
    916     eglinfo = utils.system_output(cmd, retain_output=False, ignore_status=False)
    917     # EGL version string: 1.4 (DRI2)
    918     version = re.findall(r'EGL version string: ([0-9]+).([0-9]+)', eglinfo)
    919     if version:
    920         version_major = int(version[0][0])
    921         version_minor = int(version[0][1])
    922         return (version_major, version_minor)
    923     return (None, None)
    924 
    925 
    926 class GraphicsKernelMemory(object):
    927     """
    928     Reads from sysfs to determine kernel gem objects and memory info.
    929     """
    930     # These are sysfs fields that will be read by this test.  For different
    931     # architectures, the sysfs field paths are different.  The "paths" are given
    932     # as lists of strings because the actual path may vary depending on the
    933     # system.  This test will read from the first sysfs path in the list that is
    934     # present.
    935     # e.g. ".../memory" vs ".../gpu_memory" -- if the system has either one of
    936     # these, the test will read from that path.
    937     amdgpu_fields = {
    938         'gem_objects': ['/sys/kernel/debug/dri/0/amdgpu_gem_info'],
    939         'memory': ['/sys/kernel/debug/dri/0/amdgpu_gtt_mm'],
    940     }
    941     arm_fields = {}
    942     exynos_fields = {
    943         'gem_objects': ['/sys/kernel/debug/dri/?/exynos_gem_objects'],
    944         'memory': ['/sys/class/misc/mali0/device/memory',
    945                    '/sys/class/misc/mali0/device/gpu_memory'],
    946     }
    947     mediatek_fields = {}
    948     # TODO(crosbug.com/p/58189) Add mediatek GPU memory nodes
    949     qualcomm_fields = {}
    950     # TODO(b/119269602) Add qualcomm GPU memory nodes once GPU patches land
    951     rockchip_fields = {}
    952     tegra_fields = {
    953         'memory': ['/sys/kernel/debug/memblock/memory'],
    954     }
    955     i915_fields = {
    956         'gem_objects': ['/sys/kernel/debug/dri/0/i915_gem_objects'],
    957         'memory': ['/sys/kernel/debug/dri/0/i915_gem_gtt'],
    958     }
    959     cirrus_fields = {}
    960     virtio_fields = {}
    961 
    962     arch_fields = {
    963         'amdgpu': amdgpu_fields,
    964         'arm': arm_fields,
    965         'cirrus': cirrus_fields,
    966         'exynos5': exynos_fields,
    967         'i915': i915_fields,
    968         'mediatek': mediatek_fields,
    969         'qualcomm': qualcomm_fields,
    970         'rockchip': rockchip_fields,
    971         'tegra': tegra_fields,
    972         'virtio': virtio_fields,
    973     }
    974 
    975 
    976     num_errors = 0
    977 
    978     def __init__(self):
    979         self._initial_memory = self.get_memory_keyvals()
    980 
    981     def get_memory_difference_keyvals(self):
    982         """
    983         Reads the graphics memory values and return the difference between now
    984         and the memory usage at initialization stage as keyvals.
    985         """
    986         current_memory = self.get_memory_keyvals()
    987         return {key: self._initial_memory[key] - current_memory[key]
    988                 for key in self._initial_memory}
    989 
    990     def get_memory_keyvals(self):
    991         """
    992         Reads the graphics memory values and returns them as keyvals.
    993         """
    994         keyvals = {}
    995 
    996         # Get architecture type and list of sysfs fields to read.
    997         soc = utils.get_cpu_soc_family()
    998 
    999         arch = utils.get_cpu_arch()
   1000         if arch == 'x86_64' or arch == 'i386':
   1001             pci_vga_device = utils.run("lspci | grep VGA").stdout.rstrip('\n')
   1002             if "Advanced Micro Devices" in pci_vga_device:
   1003                 soc = 'amdgpu'
   1004             elif "Intel Corporation" in pci_vga_device:
   1005                 soc = 'i915'
   1006             elif "Cirrus Logic" in pci_vga_device:
   1007                 # Used on qemu with kernels 3.18 and lower. Limited to 800x600
   1008                 # resolution.
   1009                 soc = 'cirrus'
   1010             else:
   1011                 pci_vga_device = utils.run('lshw -c video').stdout.rstrip()
   1012                 groups = re.search('configuration:.*driver=(\S*)',
   1013                                    pci_vga_device)
   1014                 if groups and 'virtio' in groups.group(1):
   1015                     soc = 'virtio'
   1016 
   1017         if not soc in self.arch_fields:
   1018             raise error.TestFail('Error: Architecture "%s" not yet supported.' % soc)
   1019         fields = self.arch_fields[soc]
   1020 
   1021         for field_name in fields:
   1022             possible_field_paths = fields[field_name]
   1023             field_value = None
   1024             for path in possible_field_paths:
   1025                 if utils.system('ls %s' % path):
   1026                     continue
   1027                 field_value = utils.system_output('cat %s' % path)
   1028                 break
   1029 
   1030             if not field_value:
   1031                 logging.error('Unable to find any sysfs paths for field "%s"',
   1032                               field_name)
   1033                 self.num_errors += 1
   1034                 continue
   1035 
   1036             parsed_results = GraphicsKernelMemory._parse_sysfs(field_value)
   1037 
   1038             for key in parsed_results:
   1039                 keyvals['%s_%s' % (field_name, key)] = parsed_results[key]
   1040 
   1041             if 'bytes' in parsed_results and parsed_results['bytes'] == 0:
   1042                 logging.error('%s reported 0 bytes', field_name)
   1043                 self.num_errors += 1
   1044 
   1045         keyvals['meminfo_MemUsed'] = (utils.read_from_meminfo('MemTotal') -
   1046                                       utils.read_from_meminfo('MemFree'))
   1047         keyvals['meminfo_SwapUsed'] = (utils.read_from_meminfo('SwapTotal') -
   1048                                        utils.read_from_meminfo('SwapFree'))
   1049         return keyvals
   1050 
   1051     @staticmethod
   1052     def _parse_sysfs(output):
   1053         """
   1054         Parses output of graphics memory sysfs to determine the number of
   1055         buffer objects and bytes.
   1056 
   1057         Arguments:
   1058             output      Unprocessed sysfs output
   1059         Return value:
   1060             Dictionary containing integer values of number bytes and objects.
   1061             They may have the keys 'bytes' and 'objects', respectively.  However
   1062             the result may not contain both of these values.
   1063         """
   1064         results = {}
   1065         labels = ['bytes', 'objects']
   1066 
   1067         for line in output.split('\n'):
   1068             # Strip any commas to make parsing easier.
   1069             line_words = line.replace(',', '').split()
   1070 
   1071             prev_word = None
   1072             for word in line_words:
   1073                 # When a label has been found, the previous word should be the
   1074                 # value. e.g. "3200 bytes"
   1075                 if word in labels and word not in results and prev_word:
   1076                     logging.info(prev_word)
   1077                     results[word] = int(prev_word)
   1078 
   1079                 prev_word = word
   1080 
   1081             # Once all values has been parsed, return.
   1082             if len(results) == len(labels):
   1083                 return results
   1084 
   1085         return results
   1086 
   1087 
   1088 class GraphicsStateChecker(object):
   1089     """
   1090     Analyzes the state of the GPU and log history. Should be instantiated at the
   1091     beginning of each graphics_* test.
   1092     """
   1093     crash_blacklist = []
   1094     dirty_writeback_centisecs = 0
   1095     existing_hangs = {}
   1096 
   1097     _BROWSER_VERSION_COMMAND = '/opt/google/chrome/chrome --version'
   1098     _HANGCHECK = ['drm:i915_hangcheck_elapsed', 'drm:i915_hangcheck_hung',
   1099                   'Hangcheck timer elapsed...',
   1100                   'drm/i915: Resetting chip after gpu hang']
   1101     _HANGCHECK_WARNING = ['render ring idle']
   1102     _MESSAGES_FILE = '/var/log/messages'
   1103 
   1104     def __init__(self, raise_error_on_hang=True, run_on_sw_rasterizer=False):
   1105         """
   1106         Analyzes the initial state of the GPU and log history.
   1107         """
   1108         # Attempt flushing system logs every second instead of every 10 minutes.
   1109         self.dirty_writeback_centisecs = utils.get_dirty_writeback_centisecs()
   1110         utils.set_dirty_writeback_centisecs(100)
   1111         self._raise_error_on_hang = raise_error_on_hang
   1112         logging.info(utils.get_board_with_frequency_and_memory())
   1113         self.graphics_kernel_memory = GraphicsKernelMemory()
   1114         self._run_on_sw_rasterizer = run_on_sw_rasterizer
   1115 
   1116         if utils.get_cpu_arch() != 'arm':
   1117             if not self._run_on_sw_rasterizer and is_sw_rasterizer():
   1118                 raise error.TestFail('Refusing to run on SW rasterizer.')
   1119             logging.info('Initialize: Checking for old GPU hangs...')
   1120             messages = open(self._MESSAGES_FILE, 'r')
   1121             for line in messages:
   1122                 for hang in self._HANGCHECK:
   1123                     if hang in line:
   1124                         logging.info(line)
   1125                         self.existing_hangs[line] = line
   1126             messages.close()
   1127 
   1128     def finalize(self):
   1129         """
   1130         Analyzes the state of the GPU, log history and emits warnings or errors
   1131         if the state changed since initialize. Also makes a note of the Chrome
   1132         version for later usage in the perf-dashboard.
   1133         """
   1134         utils.set_dirty_writeback_centisecs(self.dirty_writeback_centisecs)
   1135         new_gpu_hang = False
   1136         new_gpu_warning = False
   1137         if utils.get_cpu_arch() != 'arm':
   1138             logging.info('Cleanup: Checking for new GPU hangs...')
   1139             messages = open(self._MESSAGES_FILE, 'r')
   1140             for line in messages:
   1141                 for hang in self._HANGCHECK:
   1142                     if hang in line:
   1143                         if not line in self.existing_hangs.keys():
   1144                             logging.info(line)
   1145                             for warn in self._HANGCHECK_WARNING:
   1146                                 if warn in line:
   1147                                     new_gpu_warning = True
   1148                                     logging.warning(
   1149                                         'Saw GPU hang warning during test.')
   1150                                 else:
   1151                                     logging.warning('Saw GPU hang during test.')
   1152                                     new_gpu_hang = True
   1153             messages.close()
   1154 
   1155             if not self._run_on_sw_rasterizer and is_sw_rasterizer():
   1156                 logging.warning('Finished test on SW rasterizer.')
   1157                 raise error.TestFail('Finished test on SW rasterizer.')
   1158             if self._raise_error_on_hang and new_gpu_hang:
   1159                 raise error.TestError('Detected GPU hang during test.')
   1160             if new_gpu_hang:
   1161                 raise error.TestWarn('Detected GPU hang during test.')
   1162             if new_gpu_warning:
   1163                 raise error.TestWarn('Detected GPU warning during test.')
   1164 
   1165     def get_memory_access_errors(self):
   1166         """ Returns the number of errors while reading memory stats. """
   1167         return self.graphics_kernel_memory.num_errors
   1168 
   1169     def get_memory_difference_keyvals(self):
   1170         return self.graphics_kernel_memory.get_memory_difference_keyvals()
   1171 
   1172     def get_memory_keyvals(self):
   1173         """ Returns memory stats. """
   1174         return self.graphics_kernel_memory.get_memory_keyvals()
   1175 
   1176 class GraphicsApiHelper(object):
   1177     """
   1178     Report on the available graphics APIs.
   1179     Ex. gles2, gles3, gles31, and vk
   1180     """
   1181     _supported_apis = []
   1182 
   1183     DEQP_BASEDIR = os.path.join('/usr', 'local', 'deqp')
   1184     DEQP_EXECUTABLE = {
   1185         'gles2': os.path.join('modules', 'gles2', 'deqp-gles2'),
   1186         'gles3': os.path.join('modules', 'gles3', 'deqp-gles3'),
   1187         'gles31': os.path.join('modules', 'gles31', 'deqp-gles31'),
   1188         'vk': os.path.join('external', 'vulkancts', 'modules',
   1189                            'vulkan', 'deqp-vk')
   1190     }
   1191 
   1192     def __init__(self):
   1193         # Determine which executable should be run. Right now never egl.
   1194         major, minor = get_gles_version()
   1195         logging.info('Found gles%d.%d.', major, minor)
   1196         if major is None or minor is None:
   1197             raise error.TestFail(
   1198                 'Failed: Could not get gles version information (%d, %d).' %
   1199                 (major, minor)
   1200             )
   1201         if major >= 2:
   1202             self._supported_apis.append('gles2')
   1203         if major >= 3:
   1204             self._supported_apis.append('gles3')
   1205             if major > 3 or minor >= 1:
   1206                 self._supported_apis.append('gles31')
   1207 
   1208         # If libvulkan is installed, then assume the board supports vulkan.
   1209         has_libvulkan = False
   1210         for libdir in ('/usr/lib', '/usr/lib64',
   1211                        '/usr/local/lib', '/usr/local/lib64'):
   1212             if os.path.exists(os.path.join(libdir, 'libvulkan.so')):
   1213                 has_libvulkan = True
   1214 
   1215         if has_libvulkan:
   1216             executable_path = os.path.join(
   1217                 self.DEQP_BASEDIR,
   1218                 self.DEQP_EXECUTABLE['vk']
   1219             )
   1220             if os.path.exists(executable_path):
   1221                 self._supported_apis.append('vk')
   1222             else:
   1223                 logging.warning('Found libvulkan.so but did not find deqp-vk '
   1224                                 'binary for testing.')
   1225 
   1226     def get_supported_apis(self):
   1227         """Return the list of supported apis. eg. gles2, gles3, vk etc.
   1228         @returns: a copy of the supported api list will be returned
   1229         """
   1230         return list(self._supported_apis)
   1231 
   1232     def get_deqp_executable(self, api):
   1233         """Return the path to the api executable."""
   1234         if api not in self.DEQP_EXECUTABLE:
   1235             raise KeyError(
   1236                 "%s is not a supported api for GraphicsApiHelper." % api
   1237             )
   1238 
   1239         executable = os.path.join(
   1240             self.DEQP_BASEDIR,
   1241             self.DEQP_EXECUTABLE[api]
   1242         )
   1243         return executable
   1244 
   1245 # Possible paths of the kernel DRI debug text file.
   1246 _DRI_DEBUG_FILE_PATH_0 = "/sys/kernel/debug/dri/0/state"
   1247 _DRI_DEBUG_FILE_PATH_1 = "/sys/kernel/debug/dri/1/state"
   1248 
   1249 # The DRI debug file will have a lot of information, including the position and
   1250 # sizes of each plane. Some planes might be disabled but have some lingering
   1251 # crtc-pos information, those are skipped.
   1252 _CRTC_PLANE_START_PATTERN = re.compile(r'plane\[')
   1253 _CRTC_DISABLED_PLANE = re.compile(r'crtc=\(null\)')
   1254 _CRTC_POS_AND_SIZE_PATTERN = re.compile(r'crtc-pos=(?!0x0\+0\+0)')
   1255 
   1256 def get_num_hardware_overlays():
   1257     """
   1258     Counts the amount of hardware overlay planes in use.  There's always at
   1259     least 2 overlays active: the whole screen and the cursor -- unless the
   1260     cursor has never moved (e.g. in autotests), and it's not present.
   1261 
   1262     Raises: RuntimeError if the DRI debug file is not present.
   1263             OSError/IOError if the file cannot be open()ed or read().
   1264     """
   1265     file_path = _DRI_DEBUG_FILE_PATH_0;
   1266     if os.path.exists(_DRI_DEBUG_FILE_PATH_0):
   1267         file_path = _DRI_DEBUG_FILE_PATH_0;
   1268     elif os.path.exists(_DRI_DEBUG_FILE_PATH_1):
   1269         file_path = _DRI_DEBUG_FILE_PATH_1;
   1270     else:
   1271         raise RuntimeError('No DRI debug file exists (%s, %s)' %
   1272             (_DRI_DEBUG_FILE_PATH_0, _DRI_DEBUG_FILE_PATH_1))
   1273 
   1274     filetext = open(file_path).read()
   1275     logging.debug(filetext)
   1276 
   1277     matches = []
   1278     # Split the debug output by planes, skip the disabled ones and extract those
   1279     # with correct position and size information.
   1280     planes = re.split(_CRTC_PLANE_START_PATTERN, filetext)
   1281     for plane in planes:
   1282         if len(plane) == 0:
   1283             continue;
   1284         if len(re.findall(_CRTC_DISABLED_PLANE, plane)) > 0:
   1285             continue;
   1286 
   1287         matches.append(re.findall(_CRTC_POS_AND_SIZE_PATTERN, plane))
   1288 
   1289     # TODO(crbug.com/865112): return also the sizes/locations.
   1290     return len(matches)
   1291 
   1292 def is_drm_debug_supported():
   1293     """
   1294     @returns true if either of the DRI debug files are present.
   1295     """
   1296     return (os.path.exists(_DRI_DEBUG_FILE_PATH_0) or
   1297             os.path.exists(_DRI_DEBUG_FILE_PATH_1))
   1298 
   1299 # Path and file name regex defining the filesystem location for DRI devices.
   1300 _DEV_DRI_FOLDER_PATH = '/dev/dri'
   1301 _DEV_DRI_CARD_PATH = '/dev/dri/card?'
   1302 
   1303 # IOCTL code and associated parameter to set the atomic cap. Defined originally
   1304 # in the kernel's include/uapi/drm/drm.h file.
   1305 _DRM_IOCTL_SET_CLIENT_CAP = 0x4010640d
   1306 _DRM_CLIENT_CAP_ATOMIC = 3
   1307 
   1308 def is_drm_atomic_supported():
   1309     """
   1310     @returns true if there is at least a /dev/dri/card? file that seems to
   1311     support drm_atomic mode (accepts a _DRM_IOCTL_SET_CLIENT_CAP ioctl).
   1312     """
   1313     if not os.path.isdir(_DEV_DRI_FOLDER_PATH):
   1314         # This should never ever happen.
   1315         raise error.TestError('path %s inexistent', _DEV_DRI_FOLDER_PATH);
   1316 
   1317     for dev_path in glob.glob(_DEV_DRI_CARD_PATH):
   1318         try:
   1319             logging.debug('trying device %s', dev_path);
   1320             with open(dev_path, 'rw') as dev:
   1321                 # Pack a struct drm_set_client_cap: two u64.
   1322                 drm_pack = struct.pack("QQ", _DRM_CLIENT_CAP_ATOMIC, 1)
   1323                 result = fcntl.ioctl(dev, _DRM_IOCTL_SET_CLIENT_CAP, drm_pack)
   1324 
   1325                 if result is None or len(result) != len(drm_pack):
   1326                     # This should never ever happen.
   1327                     raise error.TestError('ioctl failure')
   1328 
   1329                 logging.debug('%s supports atomic', dev_path);
   1330 
   1331                 if not is_drm_debug_supported():
   1332                     raise error.TestError('platform supports DRM but there '
   1333                                           ' are no debug files for it')
   1334                 return True
   1335         except IOError as err:
   1336             logging.warning('ioctl failed on %s: %s', dev_path, str(err));
   1337 
   1338     logging.debug('No dev files seems to support atomic');
   1339     return False
   1340 
   1341 def get_max_num_available_drm_planes():
   1342     """
   1343     @returns The maximum number of DRM planes available in the system
   1344     (associated to the same CRTC), or 0 if something went wrong (e.g. modetest
   1345     failed, etc).
   1346     """
   1347 
   1348     planes = get_modetest_planes()
   1349     if len(planes) == 0:
   1350         return 0;
   1351     packed_possible_crtcs = [plane.possible_crtcs for plane in planes]
   1352     # |packed_possible_crtcs| is actually a bit field of possible CRTCs, e.g.
   1353     # 0x6 (b1001) means the plane can be associated with CRTCs index 0 and 3 but
   1354     # not with index 1 nor 2. Unpack those into |possible_crtcs|, an array of
   1355     # binary arrays.
   1356     possible_crtcs = [[int(bit) for bit in bin(crtc)[2:].zfill(16)]
   1357                          for crtc in packed_possible_crtcs]
   1358     # Accumulate the CRTCs indexes and return the maximum number of 'votes'.
   1359     return max(map(sum, zip(*possible_crtcs)))
   1360