blob: a57e9e8942dfd59973d1f9d2b3df4482d227e6e5 [file] [log] [blame]
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +08001# Copyright (c) 2011 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
5import logging
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +08006import os
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +08007import re
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +08008import tempfile
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +08009import time
10import xmlrpclib
11
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080012from autotest_lib.client.bin import utils
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080013from autotest_lib.client.common_lib import error
14from autotest_lib.server.cros.servotest import ServoTest
15
16
17class FAFTSequence(ServoTest):
18 """
19 The base class of Fully Automated Firmware Test Sequence.
20
21 Many firmware tests require several reboot cycles and verify the resulted
22 system states. To do that, an Autotest test case should detailly handle
23 every action on each step. It makes the test case hard to read and many
24 duplicated code. The base class FAFTSequence is to solve this problem.
25
26 The actions of one reboot cycle is defined in a dict, namely FAFT_STEP.
27 There are four functions in the FAFT_STEP dict:
28 state_checker: a function to check the current is valid or not,
29 returning True if valid, otherwise, False to break the whole
30 test sequence.
31 userspace_action: a function to describe the action ran in userspace.
32 reboot_action: a function to do reboot, default: software_reboot.
33 firmware_action: a function to describe the action ran after reboot.
34
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +080035 And configurations:
36 install_deps_after_boot: if True, install the Autotest dependency after
37 boot; otherwise, do nothing. It is for the cases of recovery mode
38 test. The test boots a USB/SD image instead of an internal image.
39 The previous installed Autotest dependency on the internal image
40 is lost. So need to install it again.
41
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080042 The default FAFT_STEP checks nothing in state_checker and does nothing in
43 userspace_action and firmware_action. Its reboot_action is simply a
44 software reboot. You can change the default FAFT_STEP by calling
45 self.register_faft_template(FAFT_STEP).
46
47 A FAFT test case consists of several FAFT_STEP's, namely FAFT_SEQUENCE.
48 FAFT_SEQUENCE is an array of FAFT_STEP's. Any missing fields on FAFT_STEP
49 fall back to default.
50
51 In the run_once(), it should register and run FAFT_SEQUENCE like:
52 def run_once(self):
53 self.register_faft_sequence(FAFT_SEQUENCE)
54 self.run_faft_sequnce()
55
56 Note that in the last step, we only run state_checker. The
57 userspace_action, reboot_action, and firmware_action are not executed.
58
59 Attributes:
60 _faft_template: The default FAFT_STEP of each step. The actions would
61 be over-written if the registered FAFT_SEQUENCE is valid.
62 _faft_sequence: The registered FAFT_SEQUENCE.
63 """
64 version = 1
65
66 _faft_template = None
67 _faft_sequence = ()
68
69
70 def setup(self):
71 """Autotest setup function."""
72 super(FAFTSequence, self).setup()
73 if not self._remote_infos['faft']['used']:
74 raise error.TestError('The use_faft flag should be enabled.')
75 self.register_faft_template({
76 'state_checker': (None),
77 'userspace_action': (None),
78 'reboot_action': (self.faft_client.software_reboot),
79 'firmware_action': (None)
80 })
81
82
83 def cleanup(self):
84 """Autotest cleanup function."""
85 self._faft_sequence = ()
86 self._faft_template = None
87 super(FAFTSequence, self).cleanup()
88
89
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080090 def assert_test_image_in_usb_disk(self):
91 """Assert an USB disk plugged-in on servo and a test image inside.
92
93 Raises:
94 error.TestError: if USB disk not detected or not a test image.
95 """
96 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
97 usb_dev = self.probe_host_usb_dev()
98 if not usb_dev:
99 raise error.TestError(
100 'An USB disk should be plugged in the servo board.')
101
102 tmp_dir = tempfile.mkdtemp()
Tom Wai-Hong Tamcd0e30f2011-11-02 16:44:54 +0800103 utils.system('sudo mount -r %s3 %s' % (usb_dev, tmp_dir))
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800104 code = utils.system('grep -q "Test Build" %s/etc/lsb-release' %
105 tmp_dir, ignore_status=True)
106 utils.system('sudo umount %s' % tmp_dir)
107 os.removedirs(tmp_dir)
108 if code != 0:
109 raise error.TestError(
110 'The image in the USB disk should be a test image.')
111
112
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800113 def _parse_crossystem_output(self, lines):
114 """Parse the crossystem output into a dict.
115
116 Args:
117 lines: The list of crossystem output strings.
118
119 Returns:
120 A dict which contains the crossystem keys/values.
121
122 Raises:
123 error.TestError: If wrong format in crossystem output.
124
125 >>> seq = FAFTSequence()
126 >>> seq._parse_crossystem_output([ \
127 "arch = x86 # Platform architecture", \
128 "cros_debug = 1 # OS should allow debug", \
129 ])
130 {'cros_debug': '1', 'arch': 'x86'}
131 >>> seq._parse_crossystem_output([ \
132 "arch=x86", \
133 ])
134 Traceback (most recent call last):
135 ...
136 TestError: Failed to parse crossystem output: arch=x86
137 >>> seq._parse_crossystem_output([ \
138 "arch = x86 # Platform architecture", \
139 "arch = arm # Platform architecture", \
140 ])
141 Traceback (most recent call last):
142 ...
143 TestError: Duplicated crossystem key: arch
144 """
145 pattern = "^([^ =]*) *= *(.*[^ ]) *# [^#]*$"
146 parsed_list = {}
147 for line in lines:
148 matched = re.match(pattern, line.strip())
149 if not matched:
150 raise error.TestError("Failed to parse crossystem output: %s"
151 % line)
152 (name, value) = (matched.group(1), matched.group(2))
153 if name in parsed_list:
154 raise error.TestError("Duplicated crossystem key: %s" % name)
155 parsed_list[name] = value
156 return parsed_list
157
158
159 def crossystem_checker(self, expected_dict):
160 """Check the crossystem values matched.
161
162 Given an expect_dict which describes the expected crossystem values,
163 this function check the current crossystem values are matched or not.
164
165 Args:
166 expected_dict: A dict which contains the expected values.
167
168 Returns:
169 True if the crossystem value matched; otherwise, False.
170 """
171 lines = self.faft_client.run_shell_command_get_output('crossystem')
172 got_dict = self._parse_crossystem_output(lines)
173 for key in expected_dict:
174 if key not in got_dict:
175 logging.info('Expected key "%s" not in crossystem result' % key)
176 return False
177 if isinstance(expected_dict[key], str):
178 if got_dict[key] != expected_dict[key]:
179 logging.info("Expected '%s' value '%s' but got '%s'" %
180 (key, expected_dict[key], got_dict[key]))
181 return False
182 elif isinstance(expected_dict[key], tuple):
183 # Expected value is a tuple of possible actual values.
184 if got_dict[key] not in expected_dict[key]:
185 logging.info("Expected '%s' values %s but got '%s'" %
186 (key, str(expected_dict[key]), got_dict[key]))
187 return False
188 else:
189 logging.info("The expected_dict is neither a str nor a dict.")
190 return False
191 return True
192
193
194 def _str_action(self, action):
195 """Convert the action function into a readable string.
196
197 The simple str() doesn't work on remote objects since we disable
198 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
199 So this function handles the exception in this case.
200
201 Args:
202 action: A function.
203
204 Returns:
205 A readable string.
206 """
207 try:
208 return str(action)
209 except xmlrpclib.Fault:
210 return '<remote method>'
211
212
213 def _call_action(self, action_tuple):
214 """Call the action function with/without arguments.
215
216 Args:
217 action_tuple: A function, or a tuple which consisted of a function
218 and its arguments (if any).
219
220 Returns:
221 The result value of the action function.
222 """
223 if isinstance(action_tuple, tuple):
224 action = action_tuple[0]
225 args = action_tuple[1:]
226 if callable(action):
227 logging.info('calling %s with parameter %s' % (
228 self._str_action(action), str(action_tuple[1])))
229 return action(*args)
230 else:
231 logging.info('action is not callable!')
232 else:
233 action = action_tuple
234 if action is not None:
235 if callable(action):
236 logging.info('calling %s' % self._str_action(action))
237 return action()
238 else:
239 logging.info('action is not callable!')
240
241 return None
242
243
244 def register_faft_template(self, template):
245 """Register FAFT template, the default FAFT_STEP of each step.
246
247 Args:
248 template: A FAFT_STEP dict.
249 """
250 self._faft_template = template
251
252
253 def register_faft_sequence(self, sequence):
254 """Register FAFT sequence.
255
256 Args:
257 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
258 """
259 self._faft_sequence = sequence
260
261
262 def run_faft_sequence(self):
263 """Run FAFT sequence.
264
265 Raises:
266 error.TestFail: An error when the test failed.
267 """
268 default_test = self._faft_template
269 sequence = self._faft_sequence
270
271 for test in sequence:
272 cur_test = {}
273 cur_test.update(default_test)
274 cur_test.update(test)
275
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800276 if cur_test['state_checker']:
277 if not self._call_action(cur_test['state_checker']):
278 raise error.TestFail('State checker failed!')
279
280 self._call_action(cur_test['userspace_action'])
281
282 # Don't run reboot_action and firmware_action of the last step.
283 if test is not sequence[-1]:
284 self._call_action(cur_test['reboot_action'])
285 self.wait_for_client_offline()
286 self._call_action(cur_test['firmware_action'])
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800287
288 if 'install_deps_after_boot' in cur_test:
289 self.wait_for_client(
290 install_deps=cur_test['install_deps_after_boot'])
291 else:
292 self.wait_for_client()