blob: cf3ec056a362adec2afa49d14717bd068cf6a520 [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
76 USB_PLUG_DELAY = 5
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')
109 usb_dev = self.probe_host_usb_dev()
110 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 Tamfd590c92011-11-25 11:50:57 +0800287 def setup_tried_fwb(self, tried_fwb):
288 """Setup for fw B tried state.
289
290 It makes sure the system in the requested fw B tried state. If not, it
291 tries to do so.
292
293 Args:
294 tried_fwb: True if requested in tried_fwb=1; False if tried_fwb=0.
295 """
296 if tried_fwb:
297 if not self.crossystem_checker({'tried_fwb': '1'}):
298 logging.info(
299 'Firmware is not booted with tried_fwb. Reboot into it.')
300 self.run_faft_step({
301 'userspace_action': self.faft_client.set_try_fw_b,
302 })
303 else:
304 if not self.crossystem_checker({'tried_fwb': '0'}):
305 logging.info(
306 'Firmware is booted with tried_fwb. Reboot to clear.')
307 self.run_faft_step({})
308
309
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800310 def setup_dev_mode(self, dev_mode):
311 """Setup for development mode.
312
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800313 It makes sure the system in the requested normal/dev mode. If not, it
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800314 tries to do so.
315
316 Args:
317 dev_mode: True if requested in dev mode; False if normal mode.
318 """
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800319 # Change the default firmware_action for dev mode passing the fw screen.
320 self.register_faft_template({
321 'state_checker': (None),
322 'userspace_action': (None),
323 'reboot_action': (self.sync_and_hw_reboot),
324 'firmware_action': (self.wait_fw_screen_and_ctrl_d if dev_mode
325 else None),
326 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800327 if dev_mode:
328 if not self.crossystem_checker({'devsw_cur': '1'}):
329 logging.info('Dev switch is not on. Now switch it on.')
330 self.servo.enable_development_mode()
331 if not self.crossystem_checker({'devsw_boot': '1',
332 'mainfw_type': 'developer'}):
333 logging.info('System is not in dev mode. Reboot into it.')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800334 self.run_faft_step({
335 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800336 'chromeos-firmwareupdate --mode todev && reboot')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800337 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800338 else:
339 if not self.crossystem_checker({'devsw_cur': '0'}):
340 logging.info('Dev switch is not off. Now switch it off.')
341 self.servo.disable_development_mode()
342 if not self.crossystem_checker({'devsw_boot': '0',
343 'mainfw_type': 'normal'}):
344 logging.info('System is not in normal mode. Reboot into it.')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800345 self.run_faft_step({
346 'userspace_action': (self.faft_client.run_shell_command,
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800347 'chromeos-firmwareupdate --mode tonormal && reboot')
Tom Wai-Hong Tamfd590c92011-11-25 11:50:57 +0800348 })
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800349
350
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800351 def setup_kernel(self, part):
352 """Setup for kernel test.
353
354 It makes sure both kernel A and B bootable and the current boot is
355 the requested kernel part.
356
357 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800358 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800359 """
360 self.ensure_kernel_boot(part)
361 self.copy_kernel_and_rootfs(from_part=part,
362 to_part=self.OTHER_KERNEL_MAP[part])
363 self.reset_and_prioritize_kernel(part)
364
365
366 def reset_and_prioritize_kernel(self, part):
367 """Make the requested partition highest priority.
368
369 This function also reset kerenl A and B to bootable.
370
371 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800372 part: A string of partition number to be prioritized.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800373 """
374 root_dev = self.faft_client.get_root_dev()
375 # Reset kernel A and B to bootable.
376 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
377 (self.KERNEL_MAP['a'], root_dev))
378 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
379 (self.KERNEL_MAP['b'], root_dev))
380 # Set kernel part highest priority.
381 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
382 (self.KERNEL_MAP[part], root_dev))
383
384
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800385 def sync_and_hw_reboot(self):
386 """Request the client sync and do a warm reboot.
387
388 This is the default reboot action on FAFT.
389 """
390 self.faft_client.run_shell_command('sync')
391 time.sleep(5)
392 self.servo.warm_reset()
393
394
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800395 def _str_action(self, action):
396 """Convert the action function into a readable string.
397
398 The simple str() doesn't work on remote objects since we disable
399 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
400 So this function handles the exception in this case.
401
402 Args:
403 action: A function.
404
405 Returns:
406 A readable string.
407 """
408 try:
409 return str(action)
410 except xmlrpclib.Fault:
411 return '<remote method>'
412
413
414 def _call_action(self, action_tuple):
415 """Call the action function with/without arguments.
416
417 Args:
418 action_tuple: A function, or a tuple which consisted of a function
419 and its arguments (if any).
420
421 Returns:
422 The result value of the action function.
423 """
424 if isinstance(action_tuple, tuple):
425 action = action_tuple[0]
426 args = action_tuple[1:]
427 if callable(action):
428 logging.info('calling %s with parameter %s' % (
429 self._str_action(action), str(action_tuple[1])))
430 return action(*args)
431 else:
432 logging.info('action is not callable!')
433 else:
434 action = action_tuple
435 if action is not None:
436 if callable(action):
437 logging.info('calling %s' % self._str_action(action))
438 return action()
439 else:
440 logging.info('action is not callable!')
441
442 return None
443
444
445 def register_faft_template(self, template):
446 """Register FAFT template, the default FAFT_STEP of each step.
447
448 Args:
449 template: A FAFT_STEP dict.
450 """
451 self._faft_template = template
452
453
454 def register_faft_sequence(self, sequence):
455 """Register FAFT sequence.
456
457 Args:
458 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
459 """
460 self._faft_sequence = sequence
461
462
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800463 def run_faft_step(self, step, no_reboot=False):
464 """Run a single FAFT step.
465
466 Any missing field falls back to faft_template. An empty step means
467 running the default faft_template.
468
469 Args:
470 step: A FAFT_STEP dict.
471 no_reboot: True to prevent running reboot_action and firmware_action.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800472
473 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800474 error.TestFail: An error when the test failed.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800475 """
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800476 test = {}
477 test.update(self._faft_template)
478 test.update(step)
479
480 if test['state_checker']:
481 if not self._call_action(test['state_checker']):
482 raise error.TestFail('State checker failed!')
483
484 self._call_action(test['userspace_action'])
485
486 # Don't run reboot_action and firmware_action if no_reboot is True.
487 if not no_reboot:
488 self._call_action(test['reboot_action'])
489 self.wait_for_client_offline()
490 self._call_action(test['firmware_action'])
491
492 if 'install_deps_after_boot' in test:
493 self.wait_for_client(
494 install_deps=test['install_deps_after_boot'])
495 else:
496 self.wait_for_client()
497
498
499 def run_faft_sequence(self):
500 """Run FAFT sequence which was previously registered."""
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800501 sequence = self._faft_sequence
Tom Wai-Hong Tam2c50dff2011-11-11 07:01:01 +0800502 for step in sequence:
503 # Don't reboot in the last step.
504 self.run_faft_step(step, no_reboot=(step is sequence[-1]))