blob: 01292cc5241147df7c74bff5e8627e4459b8d51c [file] [log] [blame]
Derek Beckettdb735112020-08-27 10:25:15 -07001# Lint as: python2, python3
Allen Lie7ec6632016-10-17 14:54:12 -07002# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This module provides standard functions for working with Autotest labels.
7
8There are two types of labels, plain ("webcam") or keyval
9("pool:suites"). Most of this module's functions work with keyval
10labels.
11
12Most users should use LabelsMapping, which provides a dict-like
13interface for working with keyval labels.
14
15This module also provides functions for working with cros version
16strings, which are common keyval label values.
17"""
18
Derek Beckettdb735112020-08-27 10:25:15 -070019from __future__ import absolute_import
20from __future__ import division
21from __future__ import print_function
Allen Lie7ec6632016-10-17 14:54:12 -070022import collections
Allen Lie7ec6632016-10-17 14:54:12 -070023import re
Derek Beckettdb735112020-08-27 10:25:15 -070024import six
Allen Lie7ec6632016-10-17 14:54:12 -070025
Allen Lie7ec6632016-10-17 14:54:12 -070026
27class Key(object):
28 """Enum for keyval label keys."""
29 CROS_VERSION = 'cros-version'
Rohit Makasanadf0a3a32017-06-30 13:55:18 -070030 CROS_ANDROID_VERSION = 'cheets-version'
Allen Lie7ec6632016-10-17 14:54:12 -070031 FIRMWARE_RW_VERSION = 'fwrw-version'
32 FIRMWARE_RO_VERSION = 'fwro-version'
Mary Ruthven7a3b15d2019-09-17 18:45:10 -070033 FIRMWARE_CR50_RW_VERSION = 'cr50-rw-version'
Allen Lie7ec6632016-10-17 14:54:12 -070034
35
36class LabelsMapping(collections.MutableMapping):
37 """dict-like interface for working with labels.
38
39 The constructor takes an iterable of labels, either plain or keyval.
40 Plain labels are saved internally and ignored except for converting
41 back to string labels. Keyval labels are exposed through a
42 dict-like interface (pop(), keys(), items(), etc. are all
43 supported).
44
45 When multiple keyval labels share the same key, the first one wins.
46
47 The one difference from a dict is that setting a key to None will
48 delete the corresponding keyval label, since it does not make sense
49 for a keyval label to have a None value. Prefer using del or pop()
50 instead of setting a key to None.
51
52 LabelsMapping has one method getlabels() for converting back to
53 string labels.
54 """
55
Prathmesh Prabhu68acc402017-11-09 15:24:15 -080056 def __init__(self, str_labels=()):
Allen Lie7ec6632016-10-17 14:54:12 -070057 self._plain_labels = []
58 self._keyval_map = collections.OrderedDict()
59 for str_label in str_labels:
60 self._add_label(str_label)
61
62 def _add_label(self, str_label):
Richard Barnette56db2532017-11-29 14:50:35 -080063 """Add a label string to the internal map or plain labels list."""
Allen Lie7ec6632016-10-17 14:54:12 -070064 try:
65 keyval_label = parse_keyval_label(str_label)
66 except ValueError:
67 self._plain_labels.append(str_label)
68 else:
Richard Barnette56db2532017-11-29 14:50:35 -080069 if keyval_label.key not in self._keyval_map:
Allen Lie7ec6632016-10-17 14:54:12 -070070 self._keyval_map[keyval_label.key] = keyval_label.value
71
72 def __getitem__(self, key):
73 return self._keyval_map[key]
74
75 def __setitem__(self, key, val):
76 if val is None:
77 self.pop(key, None)
78 else:
79 self._keyval_map[key] = val
80
81 def __delitem__(self, key):
82 del self._keyval_map[key]
83
84 def __iter__(self):
85 return iter(self._keyval_map)
86
87 def __len__(self):
88 return len(self._keyval_map)
89
90 def getlabels(self):
91 """Return labels as a list of strings."""
92 str_labels = self._plain_labels[:]
93 keyval_labels = (KeyvalLabel(key, value)
Derek Beckettdb735112020-08-27 10:25:15 -070094 for key, value in six.iteritems(self))
Allen Lie7ec6632016-10-17 14:54:12 -070095 str_labels.extend(format_keyval_label(label)
96 for label in keyval_labels)
97 return str_labels
98
99
100_KEYVAL_LABEL_SEP = ':'
101
102
103KeyvalLabel = collections.namedtuple('KeyvalLabel', 'key, value')
104
105
106def parse_keyval_label(str_label):
107 """Parse a string as a KeyvalLabel.
108
109 If the argument is not a valid keyval label, ValueError is raised.
110 """
111 key, value = str_label.split(_KEYVAL_LABEL_SEP, 1)
112 return KeyvalLabel(key, value)
113
114
115def format_keyval_label(keyval_label):
116 """Format a KeyvalLabel as a string."""
117 return _KEYVAL_LABEL_SEP.join(keyval_label)
118
119
120CrosVersion = collections.namedtuple(
Dan Shib2751fc2017-05-16 11:05:15 -0700121 'CrosVersion', 'group, board, milestone, version, rc')
Allen Lie7ec6632016-10-17 14:54:12 -0700122
123
124_CROS_VERSION_REGEX = (
Dan Shib2751fc2017-05-16 11:05:15 -0700125 r'^'
Dan Shi02dd0662017-05-23 11:24:32 -0700126 r'(?P<group>[a-z0-9_-]+)'
Dan Shib2751fc2017-05-16 11:05:15 -0700127 r'/'
128 r'(?P<milestone>R[0-9]+)'
129 r'-'
130 r'(?P<version>[0-9.]+)'
131 r'(-(?P<rc>rc[0-9]+))?'
132 r'$'
133)
134
135_CROS_BOARD_FROM_VERSION_REGEX = (
136 r'^'
137 r'(trybot-)?'
138 r'(?P<board>[a-z_-]+)-(release|paladin|pre-cq|test-ap|toolchain)'
139 r'/R.*'
140 r'$'
Allen Lie7ec6632016-10-17 14:54:12 -0700141)
142
143
144def parse_cros_version(version_string):
145 """Parse a string as a CrosVersion.
146
147 If the argument is not a valid cros version, ValueError is raised.
148 Example cros version string: 'lumpy-release/R27-3773.0.0-rc1'
149 """
150 match = re.search(_CROS_VERSION_REGEX, version_string)
151 if match is None:
152 raise ValueError('Invalid cros version string: %r' % version_string)
Dan Shib2751fc2017-05-16 11:05:15 -0700153 parts = match.groupdict()
154 match = re.search(_CROS_BOARD_FROM_VERSION_REGEX, version_string)
155 if match is None:
156 raise ValueError('Invalid cros version string: %r. Failed to parse '
157 'board.' % version_string)
158 parts['board'] = match.group('board')
159 return CrosVersion(**parts)
Allen Lie7ec6632016-10-17 14:54:12 -0700160
161
162def format_cros_version(cros_version):
163 """Format a CrosVersion as a string."""
164 if cros_version.rc is not None:
165 return '{group}/{milestone}-{version}-{rc}'.format(
166 **cros_version._asdict())
167 else:
Rohit Makasana37f5cf02017-06-08 17:21:25 -0700168 return '{group}/{milestone}-{version}'.format(**cros_version._asdict())