blob: 99b4078b63a0405e432168e12bae772162792152 [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)
Tom Wai-Hong Tamc7ecfca2011-12-06 11:12:31 +0800275 self.run_faft_step({
276 'userspace_action': (self.reset_and_prioritize_kernel, part),
277 })
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800278
279
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800280 def wait_fw_screen_and_ctrl_d(self):
281 """Wait for firmware warning screen and press Ctrl-D."""
282 time.sleep(self.FIRMWARE_SCREEN_DELAY)
283 self.servo.ctrl_d()
284
285
Tom Wai-Hong Tam5d2f4702011-12-06 10:42:31 +0800286 def wait_fw_screen_and_plug_usb(self):
287 """Wait for firmware warning screen and then unplug and plug the USB."""
288 time.sleep(self.FIRMWARE_SCREEN_DELAY)
289 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
290 time.sleep(self.USB_PLUG_DELAY)
291 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
292
293
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800294 def setup_tried_fwb(self, tried_fwb):
295 """Setup for fw B tried state.
296
297 It makes sure the system in the requested fw B tried state. If not, it
298 tries to do so.
299
300 Args:
301 tried_fwb: True if requested in tried_fwb=1; False if tried_fwb=0.
302 """
303 if tried_fwb:
304 if not self.crossystem_checker({'tried_fwb': '1'}):
305 logging.info(
306 'Firmware is not booted with tried_fwb. Reboot into it.')
307 self.run_faft_step({
308 'userspace_action': self.faft_client.set_try_fw_b,
309 })
310 else:
311 if not self.crossystem_checker({'tried_fwb': '0'}):
312 logging.info(
313 'Firmware is booted with tried_fwb. Reboot to clear.')
314 self.run_faft_step({})
315
316
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800317 def setup_dev_mode(self, dev_mode):
318 """Setup for development mode.
319
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800320 It makes sure the system in the requested normal/dev mode. If not, it
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800321 tries to do so.
322
323 Args:
324 dev_mode: True if requested in dev mode; False if normal mode.
325 """
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800326 # Change the default firmware_action for dev mode passing the fw screen.
327 self.register_faft_template({
328 'state_checker': (None),
329 'userspace_action': (None),
330 'reboot_action': (self.sync_and_hw_reboot),
331 'firmware_action': (self.wait_fw_screen_and_ctrl_d if dev_mode
332 else None),
333 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800334 if dev_mode:
335 if not self.crossystem_checker({'devsw_cur': '1'}):
336 logging.info('Dev switch is not on. Now switch it on.')
337 self.servo.enable_development_mode()
338 if not self.crossystem_checker({'devsw_boot': '1',
339 'mainfw_type': 'developer'}):
340 logging.info('System is not in dev mode. Reboot into it.')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800341 self.run_faft_step({
342 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tamc7ecfca2011-12-06 11:12:31 +0800343 'chromeos-firmwareupdate --mode todev && reboot'),
344 'reboot_action': None,
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 Tamc7ecfca2011-12-06 11:12:31 +0800355 'chromeos-firmwareupdate --mode tonormal && reboot'),
356 'reboot_action': None,
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800357 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800358
359
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800360 def setup_kernel(self, part):
361 """Setup for kernel test.
362
363 It makes sure both kernel A and B bootable and the current boot is
364 the requested kernel part.
365
366 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800367 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800368 """
369 self.ensure_kernel_boot(part)
370 self.copy_kernel_and_rootfs(from_part=part,
371 to_part=self.OTHER_KERNEL_MAP[part])
372 self.reset_and_prioritize_kernel(part)
373
374
375 def reset_and_prioritize_kernel(self, part):
376 """Make the requested partition highest priority.
377
378 This function also reset kerenl A and B to bootable.
379
380 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800381 part: A string of partition number to be prioritized.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800382 """
383 root_dev = self.faft_client.get_root_dev()
384 # Reset kernel A and B to bootable.
385 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
386 (self.KERNEL_MAP['a'], root_dev))
387 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
388 (self.KERNEL_MAP['b'], root_dev))
389 # Set kernel part highest priority.
390 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
391 (self.KERNEL_MAP[part], root_dev))
392
393
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800394 def sync_and_hw_reboot(self):
395 """Request the client sync and do a warm reboot.
396
397 This is the default reboot action on FAFT.
398 """
399 self.faft_client.run_shell_command('sync')
400 time.sleep(5)
401 self.servo.warm_reset()
402
403
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800404 def _str_action(self, action):
405 """Convert the action function into a readable string.
406
407 The simple str() doesn't work on remote objects since we disable
408 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
409 So this function handles the exception in this case.
410
411 Args:
412 action: A function.
413
414 Returns:
415 A readable string.
416 """
417 try:
418 return str(action)
419 except xmlrpclib.Fault:
420 return '<remote method>'
421
422
423 def _call_action(self, action_tuple):
424 """Call the action function with/without arguments.
425
426 Args:
427 action_tuple: A function, or a tuple which consisted of a function
428 and its arguments (if any).
429
430 Returns:
431 The result value of the action function.
432 """
433 if isinstance(action_tuple, tuple):
434 action = action_tuple[0]
435 args = action_tuple[1:]
436 if callable(action):
437 logging.info('calling %s with parameter %s' % (
438 self._str_action(action), str(action_tuple[1])))
439 return action(*args)
440 else:
441 logging.info('action is not callable!')
442 else:
443 action = action_tuple
444 if action is not None:
445 if callable(action):
446 logging.info('calling %s' % self._str_action(action))
447 return action()
448 else:
449 logging.info('action is not callable!')
450
451 return None
452
453
454 def register_faft_template(self, template):
455 """Register FAFT template, the default FAFT_STEP of each step.
456
457 Args:
458 template: A FAFT_STEP dict.
459 """
460 self._faft_template = template
461
462
463 def register_faft_sequence(self, sequence):
464 """Register FAFT sequence.
465
466 Args:
467 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
468 """
469 self._faft_sequence = sequence
470
471
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800472 def run_faft_step(self, step, no_reboot=False):
473 """Run a single FAFT step.
474
475 Any missing field falls back to faft_template. An empty step means
476 running the default faft_template.
477
478 Args:
479 step: A FAFT_STEP dict.
480 no_reboot: True to prevent running reboot_action and firmware_action.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800481
482 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800483 error.TestFail: An error when the test failed.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800484 """
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800485 test = {}
486 test.update(self._faft_template)
487 test.update(step)
488
489 if test['state_checker']:
490 if not self._call_action(test['state_checker']):
491 raise error.TestFail('State checker failed!')
492
493 self._call_action(test['userspace_action'])
494
495 # Don't run reboot_action and firmware_action if no_reboot is True.
496 if not no_reboot:
497 self._call_action(test['reboot_action'])
498 self.wait_for_client_offline()
499 self._call_action(test['firmware_action'])
500
501 if 'install_deps_after_boot' in test:
502 self.wait_for_client(
503 install_deps=test['install_deps_after_boot'])
504 else:
505 self.wait_for_client()
506
507
508 def run_faft_sequence(self):
509 """Run FAFT sequence which was previously registered."""
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800510 sequence = self._faft_sequence
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800511 for step in sequence:
512 # Don't reboot in the last step.
513 self.run_faft_step(step, no_reboot=(step is sequence[-1]))