blob: 4502a0619c423895017f41616eaea7f94e7e82f8 [file] [log] [blame]
Kevin Chenga2619dc2016-03-28 11:42:08 -07001# Copyright 2016 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"""This class defines the CrosHost Label class."""
6
C Shapiro05dd3222017-09-22 10:42:33 -06007import collections
Kevin Chenga2619dc2016-03-28 11:42:08 -07008import logging
9import os
10import re
11
12import common
13
14from autotest_lib.client.bin import utils
Kevin Cheng80ad5732016-03-31 16:01:56 -070015from autotest_lib.client.common_lib import global_config
Kevin Chenga2619dc2016-03-28 11:42:08 -070016from autotest_lib.client.cros.audio import cras_utils
Kevin Chenga2619dc2016-03-28 11:42:08 -070017from autotest_lib.client.cros.video import constants as video_test_constants
18from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
19from autotest_lib.server.hosts import base_label
20from autotest_lib.server.hosts import common_label
Kevin Chengd9dfa582016-05-04 09:37:34 -070021from autotest_lib.server.hosts import servo_host
Kevin Cheng80ad5732016-03-31 16:01:56 -070022from autotest_lib.site_utils import hwid_lib
Kevin Chenga2619dc2016-03-28 11:42:08 -070023
24# pylint: disable=missing-docstring
C Shapiro05dd3222017-09-22 10:42:33 -060025LsbOutput = collections.namedtuple('LsbOutput', ['unibuild', 'board'])
26
27def _parse_lsb_output(host):
28 """Parses the LSB output and returns key data points for labeling.
29
30 @param host: Host that the command will be executed against
31 @returns: LsbOutput with the result of parsing the /etc/lsb-release output
32 """
33 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
34 run_method=host.run)
35
36 unibuild = release_info.get('CHROMEOS_RELEASE_UNIBUILD') == '1'
37 return LsbOutput(unibuild, release_info['CHROMEOS_RELEASE_BOARD'])
38
Kevin Chenga2619dc2016-03-28 11:42:08 -070039
Kevin Chenga8455302016-08-31 20:54:41 +000040class BoardLabel(base_label.StringPrefixLabel):
41 """Determine the correct board label for the device."""
42
43 _NAME = ds_constants.BOARD_PREFIX.rstrip(':')
44
45 def generate_labels(self, host):
46 # We only want to apply the board labels once, which is when they get
47 # added to the AFE. That way we don't have to worry about the board
48 # label switching on us if the wrong builds get put on the devices.
49 # crbug.com/624207 records one event of the board label switching
50 # unexpectedly on us.
51 for label in host._afe_host.labels:
52 if label.startswith(self._NAME + ':'):
53 return [label.split(':')[-1]]
54
C Shapiro10970222017-10-24 08:55:55 -060055 return [_parse_lsb_output(host).board]
Kevin Chenga8455302016-08-31 20:54:41 +000056
57
C Shapirob05c00b2017-07-18 15:06:49 -060058class ModelLabel(base_label.StringPrefixLabel):
59 """Determine the correct model label for the device."""
60
61 _NAME = ds_constants.MODEL_LABEL
62
63 def generate_labels(self, host):
C Shapirod7ba4a72018-01-16 17:04:35 -070064 # Based on the issue explained in BoardLabel, return the existing
65 # label if it has already been set once.
66 for label in host._afe_host.labels:
67 if label.startswith(self._NAME + ':'):
68 return [label.split(':')[-1]]
C Shapirob05c00b2017-07-18 15:06:49 -060069
C Shapiro32700032017-11-03 12:46:55 -060070 lsb_output = _parse_lsb_output(host)
71 model = None
72
73 if lsb_output.unibuild:
C Shapiro26fb1012017-12-14 16:38:03 -070074 test_label_cmd = 'cros_config / test-label'
75 result = host.run(command=test_label_cmd, ignore_status=True)
C Shapiro32700032017-11-03 12:46:55 -060076 if result.exit_status == 0:
77 model = result.stdout.strip()
C Shapiro26fb1012017-12-14 16:38:03 -070078 if not model:
79 mosys_cmd = 'mosys platform model'
80 result = host.run(command=mosys_cmd, ignore_status=True)
81 if result.exit_status == 0:
82 model = result.stdout.strip()
C Shapiro32700032017-11-03 12:46:55 -060083
84 # We need some sort of backwards compatibility for boards that
85 # are not yet supported with mosys and unified builds.
86 # This is necessary so that we can begin changing cbuildbot to take
87 # advantage of the model/board label differentiations for
88 # scheduling, while still retaining backwards compatibility.
89 return [model or lsb_output.board]
C Shapirob05c00b2017-07-18 15:06:49 -060090
91
Kevin Chenga2619dc2016-03-28 11:42:08 -070092class LightSensorLabel(base_label.BaseLabel):
93 """Label indicating if a light sensor is detected."""
94
95 _NAME = 'lightsensor'
96 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
97 _LIGHTSENSOR_FILES = [
98 "in_illuminance0_input",
99 "in_illuminance_input",
100 "in_illuminance0_raw",
101 "in_illuminance_raw",
102 "illuminance0_input",
103 ]
104
105 def exists(self, host):
106 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
107 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
108 # Run the search cmd following the symlinks. Stderr_tee is set to
109 # None as there can be a symlink loop, but the command will still
110 # execute correctly with a few messages printed to stderr.
111 result = host.run(search_cmd, stdout_tee=None, stderr_tee=None,
112 ignore_status=True)
113
114 return result.exit_status == 0
115
116
117class BluetoothLabel(base_label.BaseLabel):
118 """Label indicating if bluetooth is detected."""
119
120 _NAME = 'bluetooth'
121
122 def exists(self, host):
123 result = host.run('test -d /sys/class/bluetooth/hci0',
124 ignore_status=True)
125
126 return result.exit_status == 0
127
128
129class ECLabel(base_label.BaseLabel):
130 """Label to determine the type of EC on this host."""
131
132 _NAME = 'ec:cros'
133
134 def exists(self, host):
135 cmd = 'mosys ec info'
136 # The output should look like these, so that the last field should
137 # match our EC version scheme:
138 #
139 # stm | stm32f100 | snow_v1.3.139-375eb9f
140 # ti | Unknown-10de | peppy_v1.5.114-5d52788
141 #
142 # Non-Chrome OS ECs will look like these:
143 #
144 # ENE | KB932 | 00BE107A00
145 # ite | it8518 | 3.08
146 #
147 # And some systems don't have ECs at all (Lumpy, for example).
148 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
149
150 ecinfo = host.run(command=cmd, ignore_status=True)
151 if ecinfo.exit_status == 0:
152 res = re.search(regexp, ecinfo.stdout)
153 if res:
154 logging.info("EC version is %s", res.groups()[0])
155 return True
156 logging.info("%s got: %s", cmd, ecinfo.stdout)
157 # Has an EC, but it's not a Chrome OS EC
158 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
159 return False
160
161
162class AccelsLabel(base_label.BaseLabel):
163 """Determine the type of accelerometers on this host."""
164
165 _NAME = 'accel:cros-ec'
166
167 def exists(self, host):
168 # Check to make sure we have ectool
169 rv = host.run('which ectool', ignore_status=True)
170 if rv.exit_status:
171 logging.info("No ectool cmd found; assuming no EC accelerometers")
172 return False
173
174 # Check that the EC supports the motionsense command
Kevin Cheng856f8a32016-03-31 16:08:08 -0700175 rv = host.run('ectool motionsense', ignore_status=True)
Kevin Chenga2619dc2016-03-28 11:42:08 -0700176 if rv.exit_status:
177 logging.info("EC does not support motionsense command; "
178 "assuming no EC accelerometers")
179 return False
180
181 # Check that EC motion sensors are active
Kevin Cheng17e2f002016-05-04 08:48:03 -0700182 active = host.run('ectool motionsense active').stdout.split('\n')
Kevin Chenga2619dc2016-03-28 11:42:08 -0700183 if active[0] == "0":
184 logging.info("Motion sense inactive; assuming no EC accelerometers")
185 return False
186
187 logging.info("EC accelerometers found")
188 return True
189
190
191class ChameleonLabel(base_label.BaseLabel):
192 """Determine if a Chameleon is connected to this host."""
193
194 _NAME = 'chameleon'
195
196 def exists(self, host):
197 return host._chameleon_host is not None
198
199
200class ChameleonConnectionLabel(base_label.StringPrefixLabel):
201 """Return the Chameleon connection label."""
202
203 _NAME = 'chameleon'
204
205 def exists(self, host):
206 return host._chameleon_host is not None
207
Joseph Hwangeac44312016-08-31 12:08:38 +0800208
Kevin Chenga2619dc2016-03-28 11:42:08 -0700209 def generate_labels(self, host):
210 return [host.chameleon.get_label()]
211
212
Joseph Hwangeac44312016-08-31 12:08:38 +0800213class ChameleonPeripheralsLabel(base_label.StringPrefixLabel):
214 """Return the Chameleon peripherals labels.
215
216 The 'chameleon:bt_hid' label is applied if the bluetooth
217 classic hid device, i.e, RN-42 emulation kit, is detected.
218
219 Any peripherals plugged into the chameleon board would be
220 detected and applied proper labels in this class.
221 """
222
223 _NAME = 'chameleon'
224
225 def exists(self, host):
226 return host._chameleon_host is not None
227
228
229 def generate_labels(self, host):
230 bt_hid_device = host.chameleon.get_bluetooh_hid_mouse()
231 return ['bt_hid'] if bt_hid_device.CheckSerialConnection() else []
232
233
Kevin Chenga2619dc2016-03-28 11:42:08 -0700234class AudioLoopbackDongleLabel(base_label.BaseLabel):
235 """Return the label if an audio loopback dongle is plugged in."""
236
237 _NAME = 'audio_loopback_dongle'
238
239 def exists(self, host):
240 nodes_info = host.run(command=cras_utils.get_cras_nodes_cmd(),
241 ignore_status=True).stdout
242 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
243 cras_utils.node_type_is_plugged('MIC', nodes_info)):
244 return True
245 return False
246
247
248class PowerSupplyLabel(base_label.StringPrefixLabel):
249 """
250 Return the label describing the power supply type.
251
252 Labels representing this host's power supply.
253 * `power:battery` when the device has a battery intended for
254 extended use
255 * `power:AC_primary` when the device has a battery not intended
256 for extended use (for moving the machine, etc)
257 * `power:AC_only` when the device has no battery at all.
258 """
259
260 _NAME = 'power'
261
262 def __init__(self):
263 self.psu_cmd_result = None
264
265
266 def exists(self, host):
267 self.psu_cmd_result = host.run(command='mosys psu type',
268 ignore_status=True)
269 return self.psu_cmd_result.stdout.strip() != 'unknown'
270
271
272 def generate_labels(self, host):
273 if self.psu_cmd_result.exit_status:
274 # The psu command for mosys is not included for all platforms. The
275 # assumption is that the device will have a battery if the command
276 # is not found.
277 return ['battery']
278 return [self.psu_cmd_result.stdout.strip()]
279
280
281class StorageLabel(base_label.StringPrefixLabel):
282 """
283 Return the label describing the storage type.
284
285 Determine if the internal device is SCSI or dw_mmc device.
286 Then check that it is SSD or HDD or eMMC or something else.
287
288 Labels representing this host's internal device type:
289 * `storage:ssd` when internal device is solid state drive
290 * `storage:hdd` when internal device is hard disk drive
291 * `storage:mmc` when internal device is mmc drive
Gwendal Grignou327fec62017-07-26 15:25:43 -0700292 * `storage:nvme` when internal device is NVMe drive
Kevin Chenga2619dc2016-03-28 11:42:08 -0700293 * None When internal device is something else or
294 when we are unable to determine the type
295 """
296
297 _NAME = 'storage'
298
299 def __init__(self):
300 self.type_str = ''
301
302
303 def exists(self, host):
304 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
305 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
306 '. /usr/share/misc/chromeos-common.sh;',
307 'load_base_vars;',
308 'get_fixed_dst_drive'])
309 rootdev = host.run(command=rootdev_cmd, ignore_status=True)
310 if rootdev.exit_status:
311 logging.info("Fail to run %s", rootdev_cmd)
312 return False
313 rootdev_str = rootdev.stdout.strip()
314
315 if not rootdev_str:
316 return False
317
318 rootdev_base = os.path.basename(rootdev_str)
319
320 mmc_pattern = '/dev/mmcblk[0-9]'
321 if re.match(mmc_pattern, rootdev_str):
322 # Use type to determine if the internal device is eMMC or somthing
323 # else. We can assume that MMC is always an internal device.
324 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
325 type = host.run(command=type_cmd, ignore_status=True)
326 if type.exit_status:
327 logging.info("Fail to run %s", type_cmd)
328 return False
329 type_str = type.stdout.strip()
330
331 if type_str == 'MMC':
332 self.type_str = 'mmc'
333 return True
334
335 scsi_pattern = '/dev/sd[a-z]+'
336 if re.match(scsi_pattern, rootdev.stdout):
337 # Read symlink for /sys/block/sd* to determine if the internal
338 # device is connected via ata or usb.
339 link_cmd = 'readlink /sys/block/%s' % rootdev_base
340 link = host.run(command=link_cmd, ignore_status=True)
341 if link.exit_status:
342 logging.info("Fail to run %s", link_cmd)
343 return False
344 link_str = link.stdout.strip()
345 if 'usb' in link_str:
346 return False
347
348 # Read rotation to determine if the internal device is ssd or hdd.
349 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
350 % rootdev_base)
351 rotate = host.run(command=rotate_cmd, ignore_status=True)
352 if rotate.exit_status:
353 logging.info("Fail to run %s", rotate_cmd)
354 return False
355 rotate_str = rotate.stdout.strip()
356
357 rotate_dict = {'0':'ssd', '1':'hdd'}
358 self.type_str = rotate_dict.get(rotate_str)
359 return True
360
Gwendal Grignou327fec62017-07-26 15:25:43 -0700361 nvme_pattern = '/dev/nvme[0-9]+n[0-9]+'
362 if re.match(nvme_pattern, rootdev_str):
Gwendal Grignou3660c192017-12-06 10:11:23 -0800363 self.type_str = 'nvme'
Gwendal Grignou327fec62017-07-26 15:25:43 -0700364 return True
365
Kevin Chenga2619dc2016-03-28 11:42:08 -0700366 # All other internal device / error case will always fall here
367 return False
368
369
370 def generate_labels(self, host):
371 return [self.type_str]
372
373
374class ServoLabel(base_label.BaseLabel):
375 """Label to apply if a servo is present."""
376
377 _NAME = 'servo'
378
379 def exists(self, host):
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700380 """
381 Check if the servo label should apply to the host or not.
382
383 @returns True if a servo host is detected, False otherwise.
384 """
Kevin Cheng745b8162017-05-26 09:48:36 -0700385 servo_host_hostname = None
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700386 servo_args, _ = servo_host._get_standard_servo_args(host)
Kevin Cheng745b8162017-05-26 09:48:36 -0700387 if servo_args:
388 servo_host_hostname = servo_args.get(servo_host.SERVO_HOST_ATTR)
Kevin Chengbbe5cf12016-06-08 21:50:01 -0700389 return (servo_host_hostname is not None
390 and servo_host.servo_host_is_up(servo_host_hostname))
Kevin Chenga2619dc2016-03-28 11:42:08 -0700391
392
393class VideoLabel(base_label.StringLabel):
394 """Labels detailing video capabilities."""
395
396 # List gathered from
397 # https://chromium.googlesource.com/chromiumos/
398 # platform2/+/master/avtest_label_detect/main.c#19
Hirokazu Hondad6cfe922017-10-03 13:07:37 +0900399 # TODO(hiroh): '4k_video' won't be used. It will be removed in the future.
Kevin Chenga2619dc2016-03-28 11:42:08 -0700400 _NAME = [
401 'hw_jpeg_acc_dec',
402 'hw_video_acc_h264',
403 'hw_video_acc_vp8',
404 'hw_video_acc_vp9',
405 'hw_video_acc_enc_h264',
406 'hw_video_acc_enc_vp8',
407 'webcam',
Hirokazu Honda57dbf002017-09-21 16:05:06 +0900408 '4k_video',
Hirokazu Hondad6cfe922017-10-03 13:07:37 +0900409 '4k_video_h264',
410 '4k_video_vp8',
411 '4k_video_vp9',
Kevin Chenga2619dc2016-03-28 11:42:08 -0700412 ]
413
414 def generate_labels(self, host):
415 result = host.run('/usr/local/bin/avtest_label_detect',
416 ignore_status=True).stdout
417 return re.findall('^Detected label: (\w+)$', result, re.M)
418
419
Ilja H. Friedel50290642017-12-01 19:39:53 -0800420class ArcLabel(base_label.BaseLabel):
421 """Label indicates if host has ARC support."""
422
423 _NAME = 'arc'
424
425 @base_label.forever_exists_decorate
426 def exists(self, host):
427 return 0 == host.run(
428 'grep CHROMEOS_ARC_VERSION /etc/lsb-release',
429 ignore_status=True).exit_status
430
431
432class CtsArchLabel(base_label.StringLabel):
433 """Labels to determine the abi of the CTS bundle (arm or x86 only)."""
434 # TODO(ihf): create labels for ABIs supported by container like x86_64.
Rohit Makasana5a153502016-06-13 15:50:09 -0700435
436 _NAME = ['cts_abi_arm', 'cts_abi_x86']
437
438 def _get_cts_abis(self, host):
439 """Return supported CTS ABIs.
440
441 @return List of supported CTS bundle ABIs.
442 """
443 cts_abis = {'x86_64': ['arm', 'x86'], 'arm': ['arm']}
444 return cts_abis.get(host.get_cpu_arch(), [])
445
Rohit Makasana5a153502016-06-13 15:50:09 -0700446 def generate_labels(self, host):
447 return ['cts_abi_' + abi for abi in self._get_cts_abis(host)]
448
449
Ilja H. Friedel50290642017-12-01 19:39:53 -0800450class SparseCoverageLabel(base_label.StringLabel):
451 """Label indicates if it is desirable to cover a test for this build."""
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700452
Ilja H. Friedel50290642017-12-01 19:39:53 -0800453 # Prime numbers. We can easily construct 6, 10, 15 and 30 from these.
454 _NAME = ['sparse_coverage_2', 'sparse_coverage_3', 'sparse_coverage_5']
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700455
Ilja H. Friedel50290642017-12-01 19:39:53 -0800456 def _should_cover(self, host, nth_build):
457 release_info = utils.parse_cmd_output(
458 'cat /etc/lsb-release', run_method=host.run)
459 build = release_info.get('CHROMEOS_RELEASE_BUILD_NUMBER')
460 branch = release_info.get('CHROMEOS_RELEASE_BRANCH_NUMBER')
461 patch = release_info.get('CHROMEOS_RELEASE_PATCH_NUMBER')
462 builder = release_info.get('CHROMEOS_RELEASE_BUILDER_PATH')
463 if not 'release' in builder:
464 # Sparse coverage only makes sense on release/canary builds.
465 return True
466 if patch != '0':
467 # We are on a paladin or pfq build. These are never sparse.
468 # Redundant with release check above but just in case.
469 return True
470 if branch != '0':
471 # We are on a branch. For now these are not sparse.
472 # TODO(ihf): Consider sparse coverage on beta.
473 return True
474 # Now we can be sure we are on master.
475 if int(build) % nth_build == 0:
476 # We only want to cover one in n builds on master. This is the
477 # lucky one.
478 return True
479 # We skip all other builds on master.
480 return False
481
482 def generate_labels(self, host):
483 labels = []
484 for n in [2, 3, 5]:
485 if self._should_cover(host, n):
486 labels.append('sparse_coverage_%d' % n)
487 return labels
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700488
489
Kevin Chenga2619dc2016-03-28 11:42:08 -0700490class VideoGlitchLabel(base_label.BaseLabel):
491 """Label indicates if host supports video glitch detection tests."""
492
493 _NAME = 'video_glitch_detection'
494
495 def exists(self, host):
496 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
497
498 return board in video_test_constants.SUPPORTED_BOARDS
499
500
Kevin Chenga2619dc2016-03-28 11:42:08 -0700501class InternalDisplayLabel(base_label.StringLabel):
502 """Label that determines if the device has an internal display."""
503
504 _NAME = 'internal_display'
505
506 def generate_labels(self, host):
507 from autotest_lib.client.cros.graphics import graphics_utils
508 from autotest_lib.client.common_lib import utils as common_utils
509
510 def __system_output(cmd):
511 return host.run(cmd).stdout
512
513 def __read_file(remote_path):
514 return host.run('cat %s' % remote_path).stdout
515
516 # Hijack the necessary client functions so that we can take advantage
517 # of the client lib here.
518 # FIXME: find a less hacky way than this
519 original_system_output = utils.system_output
520 original_read_file = common_utils.read_file
521 utils.system_output = __system_output
522 common_utils.read_file = __read_file
523 try:
524 return ([self._NAME]
525 if graphics_utils.has_internal_display()
526 else [])
527 finally:
528 utils.system_output = original_system_output
529 common_utils.read_file = original_read_file
530
531
532class LucidSleepLabel(base_label.BaseLabel):
533 """Label that determines if device has support for lucid sleep."""
534
535 # TODO(kevcheng): See if we can determine if this label is applicable a
536 # better way (crbug.com/592146).
537 _NAME = 'lucidsleep'
538 LUCID_SLEEP_BOARDS = ['samus', 'lulu']
539
540 def exists(self, host):
541 board = host.get_board().replace(ds_constants.BOARD_PREFIX, '')
542 return board in self.LUCID_SLEEP_BOARDS
543
544
Kevin Cheng80ad5732016-03-31 16:01:56 -0700545class HWIDLabel(base_label.StringLabel):
546 """Return all the labels generated from the hwid."""
547
548 # We leave out _NAME because hwid_lib will generate everything for us.
549
550 def __init__(self):
551 # Grab the key file needed to access the hwid service.
552 self.key_file = global_config.global_config.get_config_value(
553 'CROS', 'HWID_KEY', type=str)
554
555
556 def generate_labels(self, host):
557 hwid_labels = []
558 hwid = host.run_output('crossystem hwid').strip()
559 hwid_info_list = hwid_lib.get_hwid_info(hwid, hwid_lib.HWID_INFO_LABEL,
560 self.key_file).get('labels', [])
561
562 for hwid_info in hwid_info_list:
563 # If it's a prefix, we'll have:
564 # {'name': prefix_label, 'value': postfix_label} and create
565 # 'prefix_label:postfix_label'; otherwise it'll just be
566 # {'name': label} which should just be 'label'.
567 value = hwid_info.get('value', '')
568 name = hwid_info.get('name', '')
569 # There should always be a name but just in case there is not.
570 if name:
571 hwid_labels.append(name if not value else
572 '%s:%s' % (name, value))
573 return hwid_labels
574
575
576 def get_all_labels(self):
577 """We need to try all labels as a prefix and as standalone.
578
579 We don't know for sure which labels are prefix labels and which are
580 standalone so we try all of them as both.
581 """
582 all_hwid_labels = []
583 try:
584 all_hwid_labels = hwid_lib.get_all_possible_dut_labels(
585 self.key_file)
586 except IOError:
587 logging.error('Can not open key file: %s', self.key_file)
588 except hwid_lib.HwIdException as e:
589 logging.error('hwid service: %s', e)
590 return all_hwid_labels, all_hwid_labels
591
592
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800593class DetachableBaseLabel(base_label.BaseLabel):
594 """Label indicating if device has detachable keyboard."""
595
596 _NAME = 'detachablebase'
597
598 def exists(self, host):
599 return host.run('which hammerd', ignore_status=True).exit_status == 0
600
601
Kevin Chenga2619dc2016-03-28 11:42:08 -0700602CROS_LABELS = [
603 AccelsLabel(),
Rohit Makasanac6e5d622016-06-16 17:13:39 -0700604 ArcLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700605 AudioLoopbackDongleLabel(),
606 BluetoothLabel(),
Kevin Chenga8455302016-08-31 20:54:41 +0000607 BoardLabel(),
C Shapiro05dd3222017-09-22 10:42:33 -0600608 ModelLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700609 ChameleonConnectionLabel(),
610 ChameleonLabel(),
Joseph Hwangeac44312016-08-31 12:08:38 +0800611 ChameleonPeripheralsLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700612 common_label.OSLabel(),
Ilja H. Friedel50290642017-12-01 19:39:53 -0800613 CtsArchLabel(),
Chih-Yu Huangcaca4882018-01-30 11:21:48 +0800614 DetachableBaseLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700615 ECLabel(),
Kevin Cheng80ad5732016-03-31 16:01:56 -0700616 HWIDLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700617 InternalDisplayLabel(),
618 LightSensorLabel(),
619 LucidSleepLabel(),
620 PowerSupplyLabel(),
621 ServoLabel(),
Ilja H. Friedel50290642017-12-01 19:39:53 -0800622 SparseCoverageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700623 StorageLabel(),
Kevin Chenga2619dc2016-03-28 11:42:08 -0700624 VideoGlitchLabel(),
625 VideoLabel(),
626]