blob: 49b94853c8a1559063eee5a15eecf1545e76bc3a [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
Tom Wai-Hong Tam22b77302011-11-03 13:03:48 +080014from autotest_lib.server.cros.servo_test import ServoTest
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080015
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
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +080073 # Delay timing
74 FIRMWARE_SCREEN_DELAY = 10
75 TEXT_SCREEN_DELAY = 20
Tom Wai-Hong Tam9ca742a2011-12-05 15:48:57 +080076 USB_PLUG_DELAY = 10
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +080077
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080078 _faft_template = None
79 _faft_sequence = ()
80
81
82 def setup(self):
83 """Autotest setup function."""
84 super(FAFTSequence, self).setup()
85 if not self._remote_infos['faft']['used']:
86 raise error.TestError('The use_faft flag should be enabled.')
87 self.register_faft_template({
88 'state_checker': (None),
89 'userspace_action': (None),
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080090 'reboot_action': (self.sync_and_hw_reboot),
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080091 'firmware_action': (None)
92 })
93
94
95 def cleanup(self):
96 """Autotest cleanup function."""
97 self._faft_sequence = ()
98 self._faft_template = None
99 super(FAFTSequence, self).cleanup()
100
101
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800102 def assert_test_image_in_usb_disk(self):
103 """Assert an USB disk plugged-in on servo and a test image inside.
104
105 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800106 error.TestError: if USB disk not detected or not a test image.
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800107 """
108 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
Jon Salzc88e5b62011-11-30 14:38:54 +0800109 usb_dev = self.servo.probe_host_usb_dev()
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800110 if not usb_dev:
111 raise error.TestError(
112 'An USB disk should be plugged in the servo board.')
113
114 tmp_dir = tempfile.mkdtemp()
Tom Wai-Hong Tamcd0e30f2011-11-02 16:44:54 +0800115 utils.system('sudo mount -r %s3 %s' % (usb_dev, tmp_dir))
Tom Wai-Hong Tame77459e2011-11-03 17:19:46 +0800116 code = utils.system(
117 'grep -qE "(Test Build|testimage-channel)" %s/etc/lsb-release' %
118 tmp_dir, ignore_status=True)
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800119 utils.system('sudo umount %s' % tmp_dir)
120 os.removedirs(tmp_dir)
121 if code != 0:
122 raise error.TestError(
123 'The image in the USB disk should be a test image.')
124
125
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800126 def _parse_crossystem_output(self, lines):
127 """Parse the crossystem output into a dict.
128
129 Args:
130 lines: The list of crossystem output strings.
131
132 Returns:
133 A dict which contains the crossystem keys/values.
134
135 Raises:
136 error.TestError: If wrong format in crossystem output.
137
138 >>> seq = FAFTSequence()
139 >>> seq._parse_crossystem_output([ \
140 "arch = x86 # Platform architecture", \
141 "cros_debug = 1 # OS should allow debug", \
142 ])
143 {'cros_debug': '1', 'arch': 'x86'}
144 >>> seq._parse_crossystem_output([ \
145 "arch=x86", \
146 ])
147 Traceback (most recent call last):
148 ...
149 TestError: Failed to parse crossystem output: arch=x86
150 >>> seq._parse_crossystem_output([ \
151 "arch = x86 # Platform architecture", \
152 "arch = arm # Platform architecture", \
153 ])
154 Traceback (most recent call last):
155 ...
156 TestError: Duplicated crossystem key: arch
157 """
158 pattern = "^([^ =]*) *= *(.*[^ ]) *# [^#]*$"
159 parsed_list = {}
160 for line in lines:
161 matched = re.match(pattern, line.strip())
162 if not matched:
163 raise error.TestError("Failed to parse crossystem output: %s"
164 % line)
165 (name, value) = (matched.group(1), matched.group(2))
166 if name in parsed_list:
167 raise error.TestError("Duplicated crossystem key: %s" % name)
168 parsed_list[name] = value
169 return parsed_list
170
171
172 def crossystem_checker(self, expected_dict):
173 """Check the crossystem values matched.
174
175 Given an expect_dict which describes the expected crossystem values,
176 this function check the current crossystem values are matched or not.
177
178 Args:
179 expected_dict: A dict which contains the expected values.
180
181 Returns:
182 True if the crossystem value matched; otherwise, False.
183 """
184 lines = self.faft_client.run_shell_command_get_output('crossystem')
185 got_dict = self._parse_crossystem_output(lines)
186 for key in expected_dict:
187 if key not in got_dict:
188 logging.info('Expected key "%s" not in crossystem result' % key)
189 return False
190 if isinstance(expected_dict[key], str):
191 if got_dict[key] != expected_dict[key]:
192 logging.info("Expected '%s' value '%s' but got '%s'" %
193 (key, expected_dict[key], got_dict[key]))
194 return False
195 elif isinstance(expected_dict[key], tuple):
196 # Expected value is a tuple of possible actual values.
197 if got_dict[key] not in expected_dict[key]:
198 logging.info("Expected '%s' values %s but got '%s'" %
199 (key, str(expected_dict[key]), got_dict[key]))
200 return False
201 else:
202 logging.info("The expected_dict is neither a str nor a dict.")
203 return False
204 return True
205
206
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800207 def root_part_checker(self, expected_part):
208 """Check the partition number of the root device matched.
209
210 Args:
211 expected_part: A string containing the number of the expected root
212 partition.
213
214 Returns:
215 True if the currect root partition number matched; otherwise, False.
216 """
217 part = self.faft_client.get_root_part()
218 return self.ROOTFS_MAP[expected_part] == part[-1]
219
220
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800221 def _join_part(self, dev, part):
222 """Return a concatenated string of device and partition number.
223
224 Args:
225 dev: A string of device, e.g.'/dev/sda'.
226 part: A string of partition number, e.g.'3'.
227
228 Returns:
229 A concatenated string of device and partition number, e.g.'/dev/sda3'.
230
231 >>> seq = FAFTSequence()
232 >>> seq._join_part('/dev/sda', '3')
233 '/dev/sda3'
234 >>> seq._join_part('/dev/mmcblk0', '2')
235 '/dev/mmcblk0p2'
236 """
237 if 'mmcblk' in dev:
238 return dev + 'p' + part
239 else:
240 return dev + part
241
242
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800243 def copy_kernel_and_rootfs(self, from_part, to_part):
244 """Copy kernel and rootfs from from_part to to_part.
245
246 Args:
247 from_part: A string of partition number to be copied from.
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800248 to_part: A string of partition number to be copied to.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800249 """
250 root_dev = self.faft_client.get_root_dev()
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800251 logging.info('Copying kernel from %s to %s. Please wait...' %
252 (from_part, to_part))
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800253 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800254 (self._join_part(root_dev, self.KERNEL_MAP[from_part]),
255 self._join_part(root_dev, self.KERNEL_MAP[to_part])))
256 logging.info('Copying rootfs from %s to %s. Please wait...' %
257 (from_part, to_part))
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800258 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
Tom Wai-Hong Tamf2103be2011-11-10 07:26:56 +0800259 (self._join_part(root_dev, self.ROOTFS_MAP[from_part]),
260 self._join_part(root_dev, self.ROOTFS_MAP[to_part])))
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800261
262
263 def ensure_kernel_boot(self, part):
264 """Ensure the request kernel boot.
265
266 If not, it duplicates the current kernel to the requested kernel
267 and sets the requested higher priority to ensure it boot.
268
269 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800270 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800271 """
272 if not self.root_part_checker(part):
273 self.copy_kernel_and_rootfs(from_part=self.OTHER_KERNEL_MAP[part],
274 to_part=part)
275 self.reset_and_prioritize_kernel(part)
276 self.sync_and_hw_reboot()
277 self.wait_for_client_offline()
278 self.wait_for_client()
279
280
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800281 def wait_fw_screen_and_ctrl_d(self):
282 """Wait for firmware warning screen and press Ctrl-D."""
283 time.sleep(self.FIRMWARE_SCREEN_DELAY)
284 self.servo.ctrl_d()
285
286
Tom Wai-Hong Tam5d2f4702011-12-06 10:42:31 +0800287 def wait_fw_screen_and_plug_usb(self):
288 """Wait for firmware warning screen and then unplug and plug the USB."""
289 time.sleep(self.FIRMWARE_SCREEN_DELAY)
290 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
291 time.sleep(self.USB_PLUG_DELAY)
292 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
293
294
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800295 def setup_tried_fwb(self, tried_fwb):
296 """Setup for fw B tried state.
297
298 It makes sure the system in the requested fw B tried state. If not, it
299 tries to do so.
300
301 Args:
302 tried_fwb: True if requested in tried_fwb=1; False if tried_fwb=0.
303 """
304 if tried_fwb:
305 if not self.crossystem_checker({'tried_fwb': '1'}):
306 logging.info(
307 'Firmware is not booted with tried_fwb. Reboot into it.')
308 self.run_faft_step({
309 'userspace_action': self.faft_client.set_try_fw_b,
310 })
311 else:
312 if not self.crossystem_checker({'tried_fwb': '0'}):
313 logging.info(
314 'Firmware is booted with tried_fwb. Reboot to clear.')
315 self.run_faft_step({})
316
317
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800318 def setup_dev_mode(self, dev_mode):
319 """Setup for development mode.
320
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800321 It makes sure the system in the requested normal/dev mode. If not, it
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800322 tries to do so.
323
324 Args:
325 dev_mode: True if requested in dev mode; False if normal mode.
326 """
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800327 # Change the default firmware_action for dev mode passing the fw screen.
328 self.register_faft_template({
329 'state_checker': (None),
330 'userspace_action': (None),
331 'reboot_action': (self.sync_and_hw_reboot),
332 'firmware_action': (self.wait_fw_screen_and_ctrl_d if dev_mode
333 else None),
334 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800335 if dev_mode:
336 if not self.crossystem_checker({'devsw_cur': '1'}):
337 logging.info('Dev switch is not on. Now switch it on.')
338 self.servo.enable_development_mode()
339 if not self.crossystem_checker({'devsw_boot': '1',
340 'mainfw_type': 'developer'}):
341 logging.info('System is not in dev mode. Reboot into it.')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800342 self.run_faft_step({
343 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800344 'chromeos-firmwareupdate --mode todev && reboot')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800345 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800346 else:
347 if not self.crossystem_checker({'devsw_cur': '0'}):
348 logging.info('Dev switch is not off. Now switch it off.')
349 self.servo.disable_development_mode()
350 if not self.crossystem_checker({'devsw_boot': '0',
351 'mainfw_type': 'normal'}):
352 logging.info('System is not in normal mode. Reboot into it.')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800353 self.run_faft_step({
354 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800355 'chromeos-firmwareupdate --mode tonormal && reboot')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800356 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800357
358
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800359 def setup_kernel(self, part):
360 """Setup for kernel test.
361
362 It makes sure both kernel A and B bootable and the current boot is
363 the requested kernel part.
364
365 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800366 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800367 """
368 self.ensure_kernel_boot(part)
369 self.copy_kernel_and_rootfs(from_part=part,
370 to_part=self.OTHER_KERNEL_MAP[part])
371 self.reset_and_prioritize_kernel(part)
372
373
374 def reset_and_prioritize_kernel(self, part):
375 """Make the requested partition highest priority.
376
377 This function also reset kerenl A and B to bootable.
378
379 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800380 part: A string of partition number to be prioritized.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800381 """
382 root_dev = self.faft_client.get_root_dev()
383 # Reset kernel A and B to bootable.
384 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
385 (self.KERNEL_MAP['a'], root_dev))
386 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
387 (self.KERNEL_MAP['b'], root_dev))
388 # Set kernel part highest priority.
389 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
390 (self.KERNEL_MAP[part], root_dev))
391
392
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800393 def sync_and_hw_reboot(self):
394 """Request the client sync and do a warm reboot.
395
396 This is the default reboot action on FAFT.
397 """
398 self.faft_client.run_shell_command('sync')
399 time.sleep(5)
400 self.servo.warm_reset()
401
402
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800403 def _str_action(self, action):
404 """Convert the action function into a readable string.
405
406 The simple str() doesn't work on remote objects since we disable
407 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
408 So this function handles the exception in this case.
409
410 Args:
411 action: A function.
412
413 Returns:
414 A readable string.
415 """
416 try:
417 return str(action)
418 except xmlrpclib.Fault:
419 return '<remote method>'
420
421
422 def _call_action(self, action_tuple):
423 """Call the action function with/without arguments.
424
425 Args:
426 action_tuple: A function, or a tuple which consisted of a function
427 and its arguments (if any).
428
429 Returns:
430 The result value of the action function.
431 """
432 if isinstance(action_tuple, tuple):
433 action = action_tuple[0]
434 args = action_tuple[1:]
435 if callable(action):
436 logging.info('calling %s with parameter %s' % (
437 self._str_action(action), str(action_tuple[1])))
438 return action(*args)
439 else:
440 logging.info('action is not callable!')
441 else:
442 action = action_tuple
443 if action is not None:
444 if callable(action):
445 logging.info('calling %s' % self._str_action(action))
446 return action()
447 else:
448 logging.info('action is not callable!')
449
450 return None
451
452
453 def register_faft_template(self, template):
454 """Register FAFT template, the default FAFT_STEP of each step.
455
456 Args:
457 template: A FAFT_STEP dict.
458 """
459 self._faft_template = template
460
461
462 def register_faft_sequence(self, sequence):
463 """Register FAFT sequence.
464
465 Args:
466 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
467 """
468 self._faft_sequence = sequence
469
470
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800471 def run_faft_step(self, step, no_reboot=False):
472 """Run a single FAFT step.
473
474 Any missing field falls back to faft_template. An empty step means
475 running the default faft_template.
476
477 Args:
478 step: A FAFT_STEP dict.
479 no_reboot: True to prevent running reboot_action and firmware_action.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800480
481 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800482 error.TestFail: An error when the test failed.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800483 """
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800484 test = {}
485 test.update(self._faft_template)
486 test.update(step)
487
488 if test['state_checker']:
489 if not self._call_action(test['state_checker']):
490 raise error.TestFail('State checker failed!')
491
492 self._call_action(test['userspace_action'])
493
494 # Don't run reboot_action and firmware_action if no_reboot is True.
495 if not no_reboot:
496 self._call_action(test['reboot_action'])
497 self.wait_for_client_offline()
498 self._call_action(test['firmware_action'])
499
500 if 'install_deps_after_boot' in test:
501 self.wait_for_client(
502 install_deps=test['install_deps_after_boot'])
503 else:
504 self.wait_for_client()
505
506
507 def run_faft_sequence(self):
508 """Run FAFT sequence which was previously registered."""
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800509 sequence = self._faft_sequence
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800510 for step in sequence:
511 # Don't reboot in the last step.
512 self.run_faft_step(step, no_reboot=(step is sequence[-1]))