blob: f19317f36f49fb068de66ee4f9a114fe684505f7 [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
221 def copy_kernel_and_rootfs(self, from_part, to_part):
222 """Copy kernel and rootfs from from_part to to_part.
223
224 Args:
225 from_part: A string of partition number to be copied from.
226 to_part: A string of partition number to be copied to
227 """
228 root_dev = self.faft_client.get_root_dev()
229 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
230 (root_dev + self.KERNEL_MAP[from_part],
231 root_dev + self.KERNEL_MAP[to_part]))
232 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
233 (root_dev + self.ROOTFS_MAP[from_part],
234 root_dev + self.ROOTFS_MAP[to_part]))
235
236
237 def ensure_kernel_boot(self, part):
238 """Ensure the request kernel boot.
239
240 If not, it duplicates the current kernel to the requested kernel
241 and sets the requested higher priority to ensure it boot.
242
243 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800244 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800245 """
246 if not self.root_part_checker(part):
247 self.copy_kernel_and_rootfs(from_part=self.OTHER_KERNEL_MAP[part],
248 to_part=part)
249 self.reset_and_prioritize_kernel(part)
250 self.sync_and_hw_reboot()
251 self.wait_for_client_offline()
252 self.wait_for_client()
253
254
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800255 def wait_fw_screen_and_ctrl_d(self):
256 """Wait for firmware warning screen and press Ctrl-D."""
257 time.sleep(self.FIRMWARE_SCREEN_DELAY)
258 self.servo.ctrl_d()
259
260
261 def setup_dev_mode(self, dev_mode):
262 """Setup for development mode.
263
264 It makes sure the system in the request normal/dev mode. If not, i
265 tries to do so.
266
267 Args:
268 dev_mode: True if requested in dev mode; False if normal mode.
269 """
270 if dev_mode:
271 if not self.crossystem_checker({'devsw_cur': '1'}):
272 logging.info('Dev switch is not on. Now switch it on.')
273 self.servo.enable_development_mode()
274 if not self.crossystem_checker({'devsw_boot': '1',
275 'mainfw_type': 'developer'}):
276 logging.info('System is not in dev mode. Reboot into it.')
277 self.faft_client.run_shell_command(
278 'chromeos-firmwareupdate --mode todev && reboot')
279 self.wait_for_client_offline()
280 self.wait_fw_screen_and_ctrl_d()
281 self.wait_for_client()
282 else:
283 if not self.crossystem_checker({'devsw_cur': '0'}):
284 logging.info('Dev switch is not off. Now switch it off.')
285 self.servo.disable_development_mode()
286 if not self.crossystem_checker({'devsw_boot': '0',
287 'mainfw_type': 'normal'}):
288 logging.info('System is not in normal mode. Reboot into it.')
289 self.faft_client.run_shell_command(
290 'chromeos-firmwareupdate --mode tonormal && reboot')
291 self.wait_for_client_offline()
292 self.wait_for_client()
293
294
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800295 def setup_kernel(self, part):
296 """Setup for kernel test.
297
298 It makes sure both kernel A and B bootable and the current boot is
299 the requested kernel part.
300
301 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800302 part: A string of kernel partition number or 'a'/'b'.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800303 """
304 self.ensure_kernel_boot(part)
305 self.copy_kernel_and_rootfs(from_part=part,
306 to_part=self.OTHER_KERNEL_MAP[part])
307 self.reset_and_prioritize_kernel(part)
308
309
310 def reset_and_prioritize_kernel(self, part):
311 """Make the requested partition highest priority.
312
313 This function also reset kerenl A and B to bootable.
314
315 Args:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800316 part: A string of partition number to be prioritized.
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800317 """
318 root_dev = self.faft_client.get_root_dev()
319 # Reset kernel A and B to bootable.
320 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
321 (self.KERNEL_MAP['a'], root_dev))
322 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
323 (self.KERNEL_MAP['b'], root_dev))
324 # Set kernel part highest priority.
325 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
326 (self.KERNEL_MAP[part], root_dev))
327
328
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800329 def sync_and_hw_reboot(self):
330 """Request the client sync and do a warm reboot.
331
332 This is the default reboot action on FAFT.
333 """
334 self.faft_client.run_shell_command('sync')
335 time.sleep(5)
336 self.servo.warm_reset()
337
338
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800339 def _str_action(self, action):
340 """Convert the action function into a readable string.
341
342 The simple str() doesn't work on remote objects since we disable
343 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
344 So this function handles the exception in this case.
345
346 Args:
347 action: A function.
348
349 Returns:
350 A readable string.
351 """
352 try:
353 return str(action)
354 except xmlrpclib.Fault:
355 return '<remote method>'
356
357
358 def _call_action(self, action_tuple):
359 """Call the action function with/without arguments.
360
361 Args:
362 action_tuple: A function, or a tuple which consisted of a function
363 and its arguments (if any).
364
365 Returns:
366 The result value of the action function.
367 """
368 if isinstance(action_tuple, tuple):
369 action = action_tuple[0]
370 args = action_tuple[1:]
371 if callable(action):
372 logging.info('calling %s with parameter %s' % (
373 self._str_action(action), str(action_tuple[1])))
374 return action(*args)
375 else:
376 logging.info('action is not callable!')
377 else:
378 action = action_tuple
379 if action is not None:
380 if callable(action):
381 logging.info('calling %s' % self._str_action(action))
382 return action()
383 else:
384 logging.info('action is not callable!')
385
386 return None
387
388
389 def register_faft_template(self, template):
390 """Register FAFT template, the default FAFT_STEP of each step.
391
392 Args:
393 template: A FAFT_STEP dict.
394 """
395 self._faft_template = template
396
397
398 def register_faft_sequence(self, sequence):
399 """Register FAFT sequence.
400
401 Args:
402 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
403 """
404 self._faft_sequence = sequence
405
406
407 def run_faft_sequence(self):
408 """Run FAFT sequence.
409
410 Raises:
Tom Wai-Hong Tama9c1a502011-11-10 06:39:26 +0800411 error.TestFail: An error when the test failed.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800412 """
413 default_test = self._faft_template
414 sequence = self._faft_sequence
415
416 for test in sequence:
417 cur_test = {}
418 cur_test.update(default_test)
419 cur_test.update(test)
420
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800421 if cur_test['state_checker']:
422 if not self._call_action(cur_test['state_checker']):
423 raise error.TestFail('State checker failed!')
424
425 self._call_action(cur_test['userspace_action'])
426
427 # Don't run reboot_action and firmware_action of the last step.
428 if test is not sequence[-1]:
429 self._call_action(cur_test['reboot_action'])
430 self.wait_for_client_offline()
431 self._call_action(cur_test['firmware_action'])
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800432
433 if 'install_deps_after_boot' in cur_test:
434 self.wait_for_client(
435 install_deps=cur_test['install_deps_after_boot'])
436 else:
437 self.wait_for_client()