blob: 8fefc15bf65ab662e9138affcefc32725ac96459 [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
73
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080074 _faft_template = None
75 _faft_sequence = ()
76
77
78 def setup(self):
79 """Autotest setup function."""
80 super(FAFTSequence, self).setup()
81 if not self._remote_infos['faft']['used']:
82 raise error.TestError('The use_faft flag should be enabled.')
83 self.register_faft_template({
84 'state_checker': (None),
85 'userspace_action': (None),
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080086 'reboot_action': (self.sync_and_hw_reboot),
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080087 'firmware_action': (None)
88 })
89
90
91 def cleanup(self):
92 """Autotest cleanup function."""
93 self._faft_sequence = ()
94 self._faft_template = None
95 super(FAFTSequence, self).cleanup()
96
97
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080098 def assert_test_image_in_usb_disk(self):
99 """Assert an USB disk plugged-in on servo and a test image inside.
100
101 Raises:
102 error.TestError: if USB disk not detected or not a test image.
103 """
104 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
105 usb_dev = self.probe_host_usb_dev()
106 if not usb_dev:
107 raise error.TestError(
108 'An USB disk should be plugged in the servo board.')
109
110 tmp_dir = tempfile.mkdtemp()
Tom Wai-Hong Tamcd0e30f2011-11-02 16:44:54 +0800111 utils.system('sudo mount -r %s3 %s' % (usb_dev, tmp_dir))
Tom Wai-Hong Tame77459e2011-11-03 17:19:46 +0800112 code = utils.system(
113 'grep -qE "(Test Build|testimage-channel)" %s/etc/lsb-release' %
114 tmp_dir, ignore_status=True)
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800115 utils.system('sudo umount %s' % tmp_dir)
116 os.removedirs(tmp_dir)
117 if code != 0:
118 raise error.TestError(
119 'The image in the USB disk should be a test image.')
120
121
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800122 def _parse_crossystem_output(self, lines):
123 """Parse the crossystem output into a dict.
124
125 Args:
126 lines: The list of crossystem output strings.
127
128 Returns:
129 A dict which contains the crossystem keys/values.
130
131 Raises:
132 error.TestError: If wrong format in crossystem output.
133
134 >>> seq = FAFTSequence()
135 >>> seq._parse_crossystem_output([ \
136 "arch = x86 # Platform architecture", \
137 "cros_debug = 1 # OS should allow debug", \
138 ])
139 {'cros_debug': '1', 'arch': 'x86'}
140 >>> seq._parse_crossystem_output([ \
141 "arch=x86", \
142 ])
143 Traceback (most recent call last):
144 ...
145 TestError: Failed to parse crossystem output: arch=x86
146 >>> seq._parse_crossystem_output([ \
147 "arch = x86 # Platform architecture", \
148 "arch = arm # Platform architecture", \
149 ])
150 Traceback (most recent call last):
151 ...
152 TestError: Duplicated crossystem key: arch
153 """
154 pattern = "^([^ =]*) *= *(.*[^ ]) *# [^#]*$"
155 parsed_list = {}
156 for line in lines:
157 matched = re.match(pattern, line.strip())
158 if not matched:
159 raise error.TestError("Failed to parse crossystem output: %s"
160 % line)
161 (name, value) = (matched.group(1), matched.group(2))
162 if name in parsed_list:
163 raise error.TestError("Duplicated crossystem key: %s" % name)
164 parsed_list[name] = value
165 return parsed_list
166
167
168 def crossystem_checker(self, expected_dict):
169 """Check the crossystem values matched.
170
171 Given an expect_dict which describes the expected crossystem values,
172 this function check the current crossystem values are matched or not.
173
174 Args:
175 expected_dict: A dict which contains the expected values.
176
177 Returns:
178 True if the crossystem value matched; otherwise, False.
179 """
180 lines = self.faft_client.run_shell_command_get_output('crossystem')
181 got_dict = self._parse_crossystem_output(lines)
182 for key in expected_dict:
183 if key not in got_dict:
184 logging.info('Expected key "%s" not in crossystem result' % key)
185 return False
186 if isinstance(expected_dict[key], str):
187 if got_dict[key] != expected_dict[key]:
188 logging.info("Expected '%s' value '%s' but got '%s'" %
189 (key, expected_dict[key], got_dict[key]))
190 return False
191 elif isinstance(expected_dict[key], tuple):
192 # Expected value is a tuple of possible actual values.
193 if got_dict[key] not in expected_dict[key]:
194 logging.info("Expected '%s' values %s but got '%s'" %
195 (key, str(expected_dict[key]), got_dict[key]))
196 return False
197 else:
198 logging.info("The expected_dict is neither a str nor a dict.")
199 return False
200 return True
201
202
Tom Wai-Hong Tamcfda61f2011-11-02 17:41:01 +0800203 def root_part_checker(self, expected_part):
204 """Check the partition number of the root device matched.
205
206 Args:
207 expected_part: A string containing the number of the expected root
208 partition.
209
210 Returns:
211 True if the currect root partition number matched; otherwise, False.
212 """
213 part = self.faft_client.get_root_part()
214 return self.ROOTFS_MAP[expected_part] == part[-1]
215
216
217 def copy_kernel_and_rootfs(self, from_part, to_part):
218 """Copy kernel and rootfs from from_part to to_part.
219
220 Args:
221 from_part: A string of partition number to be copied from.
222 to_part: A string of partition number to be copied to
223 """
224 root_dev = self.faft_client.get_root_dev()
225 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
226 (root_dev + self.KERNEL_MAP[from_part],
227 root_dev + self.KERNEL_MAP[to_part]))
228 self.faft_client.run_shell_command('dd if=%s of=%s bs=4M' %
229 (root_dev + self.ROOTFS_MAP[from_part],
230 root_dev + self.ROOTFS_MAP[to_part]))
231
232
233 def ensure_kernel_boot(self, part):
234 """Ensure the request kernel boot.
235
236 If not, it duplicates the current kernel to the requested kernel
237 and sets the requested higher priority to ensure it boot.
238
239 Args:
240 part: A string of kernel partition number or 'a'/'b'.
241 """
242 if not self.root_part_checker(part):
243 self.copy_kernel_and_rootfs(from_part=self.OTHER_KERNEL_MAP[part],
244 to_part=part)
245 self.reset_and_prioritize_kernel(part)
246 self.sync_and_hw_reboot()
247 self.wait_for_client_offline()
248 self.wait_for_client()
249
250
251 def setup_kernel(self, part):
252 """Setup for kernel test.
253
254 It makes sure both kernel A and B bootable and the current boot is
255 the requested kernel part.
256
257 Args:
258 part: A string of kernel partition number or 'a'/'b'.
259 """
260 self.ensure_kernel_boot(part)
261 self.copy_kernel_and_rootfs(from_part=part,
262 to_part=self.OTHER_KERNEL_MAP[part])
263 self.reset_and_prioritize_kernel(part)
264
265
266 def reset_and_prioritize_kernel(self, part):
267 """Make the requested partition highest priority.
268
269 This function also reset kerenl A and B to bootable.
270
271 Args:
272 part: A string of partition number to be prioritized.
273 """
274 root_dev = self.faft_client.get_root_dev()
275 # Reset kernel A and B to bootable.
276 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
277 (self.KERNEL_MAP['a'], root_dev))
278 self.faft_client.run_shell_command('cgpt add -i%s -P1 -S1 -T0 %s' %
279 (self.KERNEL_MAP['b'], root_dev))
280 # Set kernel part highest priority.
281 self.faft_client.run_shell_command('cgpt prioritize -i%s %s' %
282 (self.KERNEL_MAP[part], root_dev))
283
284
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800285 def sync_and_hw_reboot(self):
286 """Request the client sync and do a warm reboot.
287
288 This is the default reboot action on FAFT.
289 """
290 self.faft_client.run_shell_command('sync')
291 time.sleep(5)
292 self.servo.warm_reset()
293
294
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800295 def _str_action(self, action):
296 """Convert the action function into a readable string.
297
298 The simple str() doesn't work on remote objects since we disable
299 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
300 So this function handles the exception in this case.
301
302 Args:
303 action: A function.
304
305 Returns:
306 A readable string.
307 """
308 try:
309 return str(action)
310 except xmlrpclib.Fault:
311 return '<remote method>'
312
313
314 def _call_action(self, action_tuple):
315 """Call the action function with/without arguments.
316
317 Args:
318 action_tuple: A function, or a tuple which consisted of a function
319 and its arguments (if any).
320
321 Returns:
322 The result value of the action function.
323 """
324 if isinstance(action_tuple, tuple):
325 action = action_tuple[0]
326 args = action_tuple[1:]
327 if callable(action):
328 logging.info('calling %s with parameter %s' % (
329 self._str_action(action), str(action_tuple[1])))
330 return action(*args)
331 else:
332 logging.info('action is not callable!')
333 else:
334 action = action_tuple
335 if action is not None:
336 if callable(action):
337 logging.info('calling %s' % self._str_action(action))
338 return action()
339 else:
340 logging.info('action is not callable!')
341
342 return None
343
344
345 def register_faft_template(self, template):
346 """Register FAFT template, the default FAFT_STEP of each step.
347
348 Args:
349 template: A FAFT_STEP dict.
350 """
351 self._faft_template = template
352
353
354 def register_faft_sequence(self, sequence):
355 """Register FAFT sequence.
356
357 Args:
358 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
359 """
360 self._faft_sequence = sequence
361
362
363 def run_faft_sequence(self):
364 """Run FAFT sequence.
365
366 Raises:
367 error.TestFail: An error when the test failed.
368 """
369 default_test = self._faft_template
370 sequence = self._faft_sequence
371
372 for test in sequence:
373 cur_test = {}
374 cur_test.update(default_test)
375 cur_test.update(test)
376
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800377 if cur_test['state_checker']:
378 if not self._call_action(cur_test['state_checker']):
379 raise error.TestFail('State checker failed!')
380
381 self._call_action(cur_test['userspace_action'])
382
383 # Don't run reboot_action and firmware_action of the last step.
384 if test is not sequence[-1]:
385 self._call_action(cur_test['reboot_action'])
386 self.wait_for_client_offline()
387 self._call_action(cur_test['firmware_action'])
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800388
389 if 'install_deps_after_boot' in cur_test:
390 self.wait_for_client(
391 install_deps=cur_test['install_deps_after_boot'])
392 else:
393 self.wait_for_client()