blob: 5c01a6b2f47327dbf3ed387ceceafa32f9a2f198 [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
287 def setup_dev_mode(self, dev_mode):
288 """Setup for development mode.
289
290 It makes sure the system in the request normal/dev mode. If not, i
291 tries to do so.
292
293 Args:
294 dev_mode: True if requested in dev mode; False if normal mode.
295 """
296 if dev_mode:
297 if not self.crossystem_checker({'devsw_cur': '1'}):
298 logging.info('Dev switch is not on. Now switch it on.')
299 self.servo.enable_development_mode()
300 if not self.crossystem_checker({'devsw_boot': '1',
301 'mainfw_type': 'developer'}):
302 logging.info('System is not in dev mode. Reboot into it.')
303 self.faft_client.run_shell_command(
304 'chromeos-firmwareupdate --mode todev && reboot')
305 self.wait_for_client_offline()
306 self.wait_fw_screen_and_ctrl_d()
307 self.wait_for_client()
308 else:
309 if not self.crossystem_checker({'devsw_cur': '0'}):
310 logging.info('Dev switch is not off. Now switch it off.')
311 self.servo.disable_development_mode()
312 if not self.crossystem_checker({'devsw_boot': '0',
313 'mainfw_type': 'normal'}):
314 logging.info('System is not in normal mode. Reboot into it.')
315 self.faft_client.run_shell_command(
316 'chromeos-firmwareupdate --mode tonormal && reboot')
317 self.wait_for_client_offline()
318 self.wait_for_client()
319
320
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800321 def setup_kernel(self, part):
322 """Setup for kernel test.
323
324 It makes sure both kernel A and B bootable and the current boot is
325 the requested kernel part.
326
327 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800328 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800329 """
330 self.ensure_kernel_boot(part)
331 self.copy_kernel_and_rootfs(from_part=part,
332 to_part=self.OTHER_KERNEL_MAP[part])
333 self.reset_and_prioritize_kernel(part)
334
335
336 def reset_and_prioritize_kernel(self, part):
337 """Make the requested partition highest priority.
338
339 This function also reset kerenl A and B to bootable.
340
341 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800342 part: A string of partition number to be prioritized.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800343 """
344 root_dev = self.faft_client.get_root_dev()
345 # Reset kernel A and B to bootable.
346 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
347 (self.KERNEL_MAP['a'], root_dev))
348 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
349 (self.KERNEL_MAP['b'], root_dev))
350 # Set kernel part highest priority.
351 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
352 (self.KERNEL_MAP[part], root_dev))
353
354
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800355 def sync_and_hw_reboot(self):
356 """Request the client sync and do a warm reboot.
357
358 This is the default reboot action on FAFT.
359 """
360 self.faft_client.run_shell_command('sync')
361 time.sleep(5)
362 self.servo.warm_reset()
363
364
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800365 def _str_action(self, action):
366 """Convert the action function into a readable string.
367
368 The simple str() doesn't work on remote objects since we disable
369 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
370 So this function handles the exception in this case.
371
372 Args:
373 action: A function.
374
375 Returns:
376 A readable string.
377 """
378 try:
379 return str(action)
380 except xmlrpclib.Fault:
381 return '<remote method>'
382
383
384 def _call_action(self, action_tuple):
385 """Call the action function with/without arguments.
386
387 Args:
388 action_tuple: A function, or a tuple which consisted of a function
389 and its arguments (if any).
390
391 Returns:
392 The result value of the action function.
393 """
394 if isinstance(action_tuple, tuple):
395 action = action_tuple[0]
396 args = action_tuple[1:]
397 if callable(action):
398 logging.info('calling %s with parameter %s' % (
399 self._str_action(action), str(action_tuple[1])))
400 return action(*args)
401 else:
402 logging.info('action is not callable!')
403 else:
404 action = action_tuple
405 if action is not None:
406 if callable(action):
407 logging.info('calling %s' % self._str_action(action))
408 return action()
409 else:
410 logging.info('action is not callable!')
411
412 return None
413
414
415 def register_faft_template(self, template):
416 """Register FAFT template, the default FAFT_STEP of each step.
417
418 Args:
419 template: A FAFT_STEP dict.
420 """
421 self._faft_template = template
422
423
424 def register_faft_sequence(self, sequence):
425 """Register FAFT sequence.
426
427 Args:
428 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
429 """
430 self._faft_sequence = sequence
431
432
433 def run_faft_sequence(self):
434 """Run FAFT sequence.
435
436 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800437 error.TestFail: An error when the test failed.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800438 """
439 default_test = self._faft_template
440 sequence = self._faft_sequence
441
442 for test in sequence:
443 cur_test = {}
444 cur_test.update(default_test)
445 cur_test.update(test)
446
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800447 if cur_test['state_checker']:
448 if not self._call_action(cur_test['state_checker']):
449 raise error.TestFail('State checker failed!')
450
451 self._call_action(cur_test['userspace_action'])
452
453 # Don't run reboot_action and firmware_action of the last step.
454 if test is not sequence[-1]:
455 self._call_action(cur_test['reboot_action'])
456 self.wait_for_client_offline()
457 self._call_action(cur_test['firmware_action'])
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800458
459 if 'install_deps_after_boot' in cur_test:
460 self.wait_for_client(
461 install_deps=cur_test['install_deps_after_boot'])
462 else:
463 self.wait_for_client()