blob: b0d8e37a823c50ad775a32625cb6e70999da813d [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
14from autotest_lib.server.cros.servotest import ServoTest
15
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
66 _faft_template = None
67 _faft_sequence = ()
68
69
70 def setup(self):
71 """Autotest setup function."""
72 super(FAFTSequence, self).setup()
73 if not self._remote_infos['faft']['used']:
74 raise error.TestError('The use_faft flag should be enabled.')
75 self.register_faft_template({
76 'state_checker': (None),
77 'userspace_action': (None),
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +080078 'reboot_action': (self.sync_and_hw_reboot),
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080079 'firmware_action': (None)
80 })
81
82
83 def cleanup(self):
84 """Autotest cleanup function."""
85 self._faft_sequence = ()
86 self._faft_template = None
87 super(FAFTSequence, self).cleanup()
88
89
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080090 def assert_test_image_in_usb_disk(self):
91 """Assert an USB disk plugged-in on servo and a test image inside.
92
93 Raises:
94 error.TestError: if USB disk not detected or not a test image.
95 """
96 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
97 usb_dev = self.probe_host_usb_dev()
98 if not usb_dev:
99 raise error.TestError(
100 'An USB disk should be plugged in the servo board.')
101
102 tmp_dir = tempfile.mkdtemp()
Tom Wai-Hong Tamcd0e30f2011-11-02 16:44:54 +0800103 utils.system('sudo mount -r %s3 %s' % (usb_dev, tmp_dir))
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800104 code = utils.system('grep -q "Test Build" %s/etc/lsb-release' %
105 tmp_dir, ignore_status=True)
106 utils.system('sudo umount %s' % tmp_dir)
107 os.removedirs(tmp_dir)
108 if code != 0:
109 raise error.TestError(
110 'The image in the USB disk should be a test image.')
111
112
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800113 def _parse_crossystem_output(self, lines):
114 """Parse the crossystem output into a dict.
115
116 Args:
117 lines: The list of crossystem output strings.
118
119 Returns:
120 A dict which contains the crossystem keys/values.
121
122 Raises:
123 error.TestError: If wrong format in crossystem output.
124
125 >>> seq = FAFTSequence()
126 >>> seq._parse_crossystem_output([ \
127 "arch = x86 # Platform architecture", \
128 "cros_debug = 1 # OS should allow debug", \
129 ])
130 {'cros_debug': '1', 'arch': 'x86'}
131 >>> seq._parse_crossystem_output([ \
132 "arch=x86", \
133 ])
134 Traceback (most recent call last):
135 ...
136 TestError: Failed to parse crossystem output: arch=x86
137 >>> seq._parse_crossystem_output([ \
138 "arch = x86 # Platform architecture", \
139 "arch = arm # Platform architecture", \
140 ])
141 Traceback (most recent call last):
142 ...
143 TestError: Duplicated crossystem key: arch
144 """
145 pattern = "^([^ =]*) *= *(.*[^ ]) *# [^#]*$"
146 parsed_list = {}
147 for line in lines:
148 matched = re.match(pattern, line.strip())
149 if not matched:
150 raise error.TestError("Failed to parse crossystem output: %s"
151 % line)
152 (name, value) = (matched.group(1), matched.group(2))
153 if name in parsed_list:
154 raise error.TestError("Duplicated crossystem key: %s" % name)
155 parsed_list[name] = value
156 return parsed_list
157
158
159 def crossystem_checker(self, expected_dict):
160 """Check the crossystem values matched.
161
162 Given an expect_dict which describes the expected crossystem values,
163 this function check the current crossystem values are matched or not.
164
165 Args:
166 expected_dict: A dict which contains the expected values.
167
168 Returns:
169 True if the crossystem value matched; otherwise, False.
170 """
171 lines = self.faft_client.run_shell_command_get_output('crossystem')
172 got_dict = self._parse_crossystem_output(lines)
173 for key in expected_dict:
174 if key not in got_dict:
175 logging.info('Expected key "%s" not in crossystem result' % key)
176 return False
177 if isinstance(expected_dict[key], str):
178 if got_dict[key] != expected_dict[key]:
179 logging.info("Expected '%s' value '%s' but got '%s'" %
180 (key, expected_dict[key], got_dict[key]))
181 return False
182 elif isinstance(expected_dict[key], tuple):
183 # Expected value is a tuple of possible actual values.
184 if got_dict[key] not in expected_dict[key]:
185 logging.info("Expected '%s' values %s but got '%s'" %
186 (key, str(expected_dict[key]), got_dict[key]))
187 return False
188 else:
189 logging.info("The expected_dict is neither a str nor a dict.")
190 return False
191 return True
192
193
Tom Wai-Hong Tamf1e34972011-11-02 17:07:04 +0800194 def sync_and_hw_reboot(self):
195 """Request the client sync and do a warm reboot.
196
197 This is the default reboot action on FAFT.
198 """
199 self.faft_client.run_shell_command('sync')
200 time.sleep(5)
201 self.servo.warm_reset()
202
203
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800204 def _str_action(self, action):
205 """Convert the action function into a readable string.
206
207 The simple str() doesn't work on remote objects since we disable
208 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
209 So this function handles the exception in this case.
210
211 Args:
212 action: A function.
213
214 Returns:
215 A readable string.
216 """
217 try:
218 return str(action)
219 except xmlrpclib.Fault:
220 return '<remote method>'
221
222
223 def _call_action(self, action_tuple):
224 """Call the action function with/without arguments.
225
226 Args:
227 action_tuple: A function, or a tuple which consisted of a function
228 and its arguments (if any).
229
230 Returns:
231 The result value of the action function.
232 """
233 if isinstance(action_tuple, tuple):
234 action = action_tuple[0]
235 args = action_tuple[1:]
236 if callable(action):
237 logging.info('calling %s with parameter %s' % (
238 self._str_action(action), str(action_tuple[1])))
239 return action(*args)
240 else:
241 logging.info('action is not callable!')
242 else:
243 action = action_tuple
244 if action is not None:
245 if callable(action):
246 logging.info('calling %s' % self._str_action(action))
247 return action()
248 else:
249 logging.info('action is not callable!')
250
251 return None
252
253
254 def register_faft_template(self, template):
255 """Register FAFT template, the default FAFT_STEP of each step.
256
257 Args:
258 template: A FAFT_STEP dict.
259 """
260 self._faft_template = template
261
262
263 def register_faft_sequence(self, sequence):
264 """Register FAFT sequence.
265
266 Args:
267 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
268 """
269 self._faft_sequence = sequence
270
271
272 def run_faft_sequence(self):
273 """Run FAFT sequence.
274
275 Raises:
276 error.TestFail: An error when the test failed.
277 """
278 default_test = self._faft_template
279 sequence = self._faft_sequence
280
281 for test in sequence:
282 cur_test = {}
283 cur_test.update(default_test)
284 cur_test.update(test)
285
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800286 if cur_test['state_checker']:
287 if not self._call_action(cur_test['state_checker']):
288 raise error.TestFail('State checker failed!')
289
290 self._call_action(cur_test['userspace_action'])
291
292 # Don't run reboot_action and firmware_action of the last step.
293 if test is not sequence[-1]:
294 self._call_action(cur_test['reboot_action'])
295 self.wait_for_client_offline()
296 self._call_action(cur_test['firmware_action'])
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800297
298 if 'install_deps_after_boot' in cur_test:
299 self.wait_for_client(
300 install_deps=cur_test['install_deps_after_boot'])
301 else:
302 self.wait_for_client()