blob: 35b1acb4c665e8a9ac11bffddb881b06af1f1341 [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.
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080032 reboot_action: a function to do reboot, default: sync_and_hw_reboot.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080033 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
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080043 userspace_action and firmware_action. Its reboot_action is a hardware
44 reboot. You can change the default FAFT_STEP by calling
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080045 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
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +080066
67 # Mapping of partition number of kernel and rootfs.
68 KERNEL_MAP = {'a':'2', 'b':'4', '2':'2', '4':'4', '3':'2', '5':'4'}
69 ROOTFS_MAP = {'a':'3', 'b':'5', '2':'3', '4':'5', '3':'3', '5':'5'}
70 OTHER_KERNEL_MAP = {'a':'4', 'b':'2', '2':'4', '4':'2', '3':'4', '5':'2'}
71 OTHER_ROOTFS_MAP = {'a':'5', 'b':'3', '2':'5', '4':'3', '3':'5', '5':'3'}
72
73
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080074 _faft_template = None
75 _faft_sequence = ()
76
77
78 def setup(self):
79 """Autotest setup function."""
80 super(FAFTSequence, self).setup()
81 if not self._remote_infos['faft']['used']:
82 raise error.TestError('The use_faft flag should be enabled.')
83 self.register_faft_template({
84 'state_checker': (None),
85 'userspace_action': (None),
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080086 'reboot_action': (self.sync_and_hw_reboot),
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080087 'firmware_action': (None)
88 })
89
90
91 def cleanup(self):
92 """Autotest cleanup function."""
93 self._faft_sequence = ()
94 self._faft_template = None
95 super(FAFTSequence, self).cleanup()
96
97
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080098 def assert_test_image_in_usb_disk(self):
99 """Assert an USB disk plugged-in on servo and a test image inside.
100
101 Raises:
102 error.TestError: if USB disk not detected or not a test image.
103 """
104 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
105 usb_dev = self.probe_host_usb_dev()
106 if not usb_dev:
107 raise error.TestError(
108 'An USB disk should be plugged in the servo board.')
109
110 tmp_dir = tempfile.mkdtemp()
Tom Wai-Hong Tamcd0e30f2011-11-02 16:44:54 +0800111 utils.system('sudo mount -r %s3 %s' % (usb_dev, tmp_dir))
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800112 code = utils.system('grep -q "Test Build" %s/etc/lsb-release' %
113 tmp_dir, ignore_status=True)
114 utils.system('sudo umount %s' % tmp_dir)
115 os.removedirs(tmp_dir)
116 if code != 0:
117 raise error.TestError(
118 'The image in the USB disk should be a test image.')
119
120
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800121 def _parse_crossystem_output(self, lines):
122 """Parse the crossystem output into a dict.
123
124 Args:
125 lines: The list of crossystem output strings.
126
127 Returns:
128 A dict which contains the crossystem keys/values.
129
130 Raises:
131 error.TestError: If wrong format in crossystem output.
132
133 >>> seq = FAFTSequence()
134 >>> seq._parse_crossystem_output([ \
135 "arch = x86 # Platform architecture", \
136 "cros_debug = 1 # OS should allow debug", \
137 ])
138 {'cros_debug': '1', 'arch': 'x86'}
139 >>> seq._parse_crossystem_output([ \
140 "arch=x86", \
141 ])
142 Traceback (most recent call last):
143 ...
144 TestError: Failed to parse crossystem output: arch=x86
145 >>> seq._parse_crossystem_output([ \
146 "arch = x86 # Platform architecture", \
147 "arch = arm # Platform architecture", \
148 ])
149 Traceback (most recent call last):
150 ...
151 TestError: Duplicated crossystem key: arch
152 """
153 pattern = "^([^ =]*) *= *(.*[^ ]) *# [^#]*$"
154 parsed_list = {}
155 for line in lines:
156 matched = re.match(pattern, line.strip())
157 if not matched:
158 raise error.TestError("Failed to parse crossystem output: %s"
159 % line)
160 (name, value) = (matched.group(1), matched.group(2))
161 if name in parsed_list:
162 raise error.TestError("Duplicated crossystem key: %s" % name)
163 parsed_list[name] = value
164 return parsed_list
165
166
167 def crossystem_checker(self, expected_dict):
168 """Check the crossystem values matched.
169
170 Given an expect_dict which describes the expected crossystem values,
171 this function check the current crossystem values are matched or not.
172
173 Args:
174 expected_dict: A dict which contains the expected values.
175
176 Returns:
177 True if the crossystem value matched; otherwise, False.
178 """
179 lines = self.faft_client.run_shell_command_get_output('crossystem')
180 got_dict = self._parse_crossystem_output(lines)
181 for key in expected_dict:
182 if key not in got_dict:
183 logging.info('Expected key "%s" not in crossystem result' % key)
184 return False
185 if isinstance(expected_dict[key], str):
186 if got_dict[key] != expected_dict[key]:
187 logging.info("Expected '%s' value '%s' but got '%s'" %
188 (key, expected_dict[key], got_dict[key]))
189 return False
190 elif isinstance(expected_dict[key], tuple):
191 # Expected value is a tuple of possible actual values.
192 if got_dict[key] not in expected_dict[key]:
193 logging.info("Expected '%s' values %s but got '%s'" %
194 (key, str(expected_dict[key]), got_dict[key]))
195 return False
196 else:
197 logging.info("The expected_dict is neither a str nor a dict.")
198 return False
199 return True
200
201
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800202 def root_part_checker(self, expected_part):
203 """Check the partition number of the root device matched.
204
205 Args:
206 expected_part: A string containing the number of the expected root
207 partition.
208
209 Returns:
210 True if the currect root partition number matched; otherwise, False.
211 """
212 part = self.faft_client.get_root_part()
213 return self.ROOTFS_MAP[expected_part] == part[-1]
214
215
216 def copy_kernel_and_rootfs(self, from_part, to_part):
217 """Copy kernel and rootfs from from_part to to_part.
218
219 Args:
220 from_part: A string of partition number to be copied from.
221 to_part: A string of partition number to be copied to
222 """
223 root_dev = self.faft_client.get_root_dev()
224 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
225 (root_dev + self.KERNEL_MAP[from_part],
226 root_dev + self.KERNEL_MAP[to_part]))
227 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
228 (root_dev + self.ROOTFS_MAP[from_part],
229 root_dev + self.ROOTFS_MAP[to_part]))
230
231
232 def ensure_kernel_boot(self, part):
233 """Ensure the request kernel boot.
234
235 If not, it duplicates the current kernel to the requested kernel
236 and sets the requested higher priority to ensure it boot.
237
238 Args:
239 part: A string of kernel partition number or 'a'/'b'.
240 """
241 if not self.root_part_checker(part):
242 self.copy_kernel_and_rootfs(from_part=self.OTHER_KERNEL_MAP[part],
243 to_part=part)
244 self.reset_and_prioritize_kernel(part)
245 self.sync_and_hw_reboot()
246 self.wait_for_client_offline()
247 self.wait_for_client()
248
249
250 def setup_kernel(self, part):
251 """Setup for kernel test.
252
253 It makes sure both kernel A and B bootable and the current boot is
254 the requested kernel part.
255
256 Args:
257 part: A string of kernel partition number or 'a'/'b'.
258 """
259 self.ensure_kernel_boot(part)
260 self.copy_kernel_and_rootfs(from_part=part,
261 to_part=self.OTHER_KERNEL_MAP[part])
262 self.reset_and_prioritize_kernel(part)
263
264
265 def reset_and_prioritize_kernel(self, part):
266 """Make the requested partition highest priority.
267
268 This function also reset kerenl A and B to bootable.
269
270 Args:
271 part: A string of partition number to be prioritized.
272 """
273 root_dev = self.faft_client.get_root_dev()
274 # Reset kernel A and B to bootable.
275 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
276 (self.KERNEL_MAP['a'], root_dev))
277 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
278 (self.KERNEL_MAP['b'], root_dev))
279 # Set kernel part highest priority.
280 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
281 (self.KERNEL_MAP[part], root_dev))
282
283
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800284 def sync_and_hw_reboot(self):
285 """Request the client sync and do a warm reboot.
286
287 This is the default reboot action on FAFT.
288 """
289 self.faft_client.run_shell_command('sync')
290 time.sleep(5)
291 self.servo.warm_reset()
292
293
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800294 def _str_action(self, action):
295 """Convert the action function into a readable string.
296
297 The simple str() doesn't work on remote objects since we disable
298 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
299 So this function handles the exception in this case.
300
301 Args:
302 action: A function.
303
304 Returns:
305 A readable string.
306 """
307 try:
308 return str(action)
309 except xmlrpclib.Fault:
310 return '<remote method>'
311
312
313 def _call_action(self, action_tuple):
314 """Call the action function with/without arguments.
315
316 Args:
317 action_tuple: A function, or a tuple which consisted of a function
318 and its arguments (if any).
319
320 Returns:
321 The result value of the action function.
322 """
323 if isinstance(action_tuple, tuple):
324 action = action_tuple[0]
325 args = action_tuple[1:]
326 if callable(action):
327 logging.info('calling %s with parameter %s' % (
328 self._str_action(action), str(action_tuple[1])))
329 return action(*args)
330 else:
331 logging.info('action is not callable!')
332 else:
333 action = action_tuple
334 if action is not None:
335 if callable(action):
336 logging.info('calling %s' % self._str_action(action))
337 return action()
338 else:
339 logging.info('action is not callable!')
340
341 return None
342
343
344 def register_faft_template(self, template):
345 """Register FAFT template, the default FAFT_STEP of each step.
346
347 Args:
348 template: A FAFT_STEP dict.
349 """
350 self._faft_template = template
351
352
353 def register_faft_sequence(self, sequence):
354 """Register FAFT sequence.
355
356 Args:
357 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
358 """
359 self._faft_sequence = sequence
360
361
362 def run_faft_sequence(self):
363 """Run FAFT sequence.
364
365 Raises:
366 error.TestFail: An error when the test failed.
367 """
368 default_test = self._faft_template
369 sequence = self._faft_sequence
370
371 for test in sequence:
372 cur_test = {}
373 cur_test.update(default_test)
374 cur_test.update(test)
375
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800376 if cur_test['state_checker']:
377 if not self._call_action(cur_test['state_checker']):
378 raise error.TestFail('State checker failed!')
379
380 self._call_action(cur_test['userspace_action'])
381
382 # Don't run reboot_action and firmware_action of the last step.
383 if test is not sequence[-1]:
384 self._call_action(cur_test['reboot_action'])
385 self.wait_for_client_offline()
386 self._call_action(cur_test['firmware_action'])
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800387
388 if 'install_deps_after_boot' in cur_test:
389 self.wait_for_client(
390 install_deps=cur_test['install_deps_after_boot'])
391 else:
392 self.wait_for_client()