blob: 468ad1b4043307ca3fb2533911cc3857c0bbb2f2 [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
6import re
7import time
8import xmlrpclib
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.server.cros.servotest import ServoTest
12
13
14class FAFTSequence(ServoTest):
15 """
16 The base class of Fully Automated Firmware Test Sequence.
17
18 Many firmware tests require several reboot cycles and verify the resulted
19 system states. To do that, an Autotest test case should detailly handle
20 every action on each step. It makes the test case hard to read and many
21 duplicated code. The base class FAFTSequence is to solve this problem.
22
23 The actions of one reboot cycle is defined in a dict, namely FAFT_STEP.
24 There are four functions in the FAFT_STEP dict:
25 state_checker: a function to check the current is valid or not,
26 returning True if valid, otherwise, False to break the whole
27 test sequence.
28 userspace_action: a function to describe the action ran in userspace.
29 reboot_action: a function to do reboot, default: software_reboot.
30 firmware_action: a function to describe the action ran after reboot.
31
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +080032 And configurations:
33 install_deps_after_boot: if True, install the Autotest dependency after
34 boot; otherwise, do nothing. It is for the cases of recovery mode
35 test. The test boots a USB/SD image instead of an internal image.
36 The previous installed Autotest dependency on the internal image
37 is lost. So need to install it again.
38
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +080039 The default FAFT_STEP checks nothing in state_checker and does nothing in
40 userspace_action and firmware_action. Its reboot_action is simply a
41 software reboot. You can change the default FAFT_STEP by calling
42 self.register_faft_template(FAFT_STEP).
43
44 A FAFT test case consists of several FAFT_STEP's, namely FAFT_SEQUENCE.
45 FAFT_SEQUENCE is an array of FAFT_STEP's. Any missing fields on FAFT_STEP
46 fall back to default.
47
48 In the run_once(), it should register and run FAFT_SEQUENCE like:
49 def run_once(self):
50 self.register_faft_sequence(FAFT_SEQUENCE)
51 self.run_faft_sequnce()
52
53 Note that in the last step, we only run state_checker. The
54 userspace_action, reboot_action, and firmware_action are not executed.
55
56 Attributes:
57 _faft_template: The default FAFT_STEP of each step. The actions would
58 be over-written if the registered FAFT_SEQUENCE is valid.
59 _faft_sequence: The registered FAFT_SEQUENCE.
60 """
61 version = 1
62
63 _faft_template = None
64 _faft_sequence = ()
65
66
67 def setup(self):
68 """Autotest setup function."""
69 super(FAFTSequence, self).setup()
70 if not self._remote_infos['faft']['used']:
71 raise error.TestError('The use_faft flag should be enabled.')
72 self.register_faft_template({
73 'state_checker': (None),
74 'userspace_action': (None),
75 'reboot_action': (self.faft_client.software_reboot),
76 'firmware_action': (None)
77 })
78
79
80 def cleanup(self):
81 """Autotest cleanup function."""
82 self._faft_sequence = ()
83 self._faft_template = None
84 super(FAFTSequence, self).cleanup()
85
86
87 def _parse_crossystem_output(self, lines):
88 """Parse the crossystem output into a dict.
89
90 Args:
91 lines: The list of crossystem output strings.
92
93 Returns:
94 A dict which contains the crossystem keys/values.
95
96 Raises:
97 error.TestError: If wrong format in crossystem output.
98
99 >>> seq = FAFTSequence()
100 >>> seq._parse_crossystem_output([ \
101 "arch = x86 # Platform architecture", \
102 "cros_debug = 1 # OS should allow debug", \
103 ])
104 {'cros_debug': '1', 'arch': 'x86'}
105 >>> seq._parse_crossystem_output([ \
106 "arch=x86", \
107 ])
108 Traceback (most recent call last):
109 ...
110 TestError: Failed to parse crossystem output: arch=x86
111 >>> seq._parse_crossystem_output([ \
112 "arch = x86 # Platform architecture", \
113 "arch = arm # Platform architecture", \
114 ])
115 Traceback (most recent call last):
116 ...
117 TestError: Duplicated crossystem key: arch
118 """
119 pattern = "^([^ =]*) *= *(.*[^ ]) *# [^#]*$"
120 parsed_list = {}
121 for line in lines:
122 matched = re.match(pattern, line.strip())
123 if not matched:
124 raise error.TestError("Failed to parse crossystem output: %s"
125 % line)
126 (name, value) = (matched.group(1), matched.group(2))
127 if name in parsed_list:
128 raise error.TestError("Duplicated crossystem key: %s" % name)
129 parsed_list[name] = value
130 return parsed_list
131
132
133 def crossystem_checker(self, expected_dict):
134 """Check the crossystem values matched.
135
136 Given an expect_dict which describes the expected crossystem values,
137 this function check the current crossystem values are matched or not.
138
139 Args:
140 expected_dict: A dict which contains the expected values.
141
142 Returns:
143 True if the crossystem value matched; otherwise, False.
144 """
145 lines = self.faft_client.run_shell_command_get_output('crossystem')
146 got_dict = self._parse_crossystem_output(lines)
147 for key in expected_dict:
148 if key not in got_dict:
149 logging.info('Expected key "%s" not in crossystem result' % key)
150 return False
151 if isinstance(expected_dict[key], str):
152 if got_dict[key] != expected_dict[key]:
153 logging.info("Expected '%s' value '%s' but got '%s'" %
154 (key, expected_dict[key], got_dict[key]))
155 return False
156 elif isinstance(expected_dict[key], tuple):
157 # Expected value is a tuple of possible actual values.
158 if got_dict[key] not in expected_dict[key]:
159 logging.info("Expected '%s' values %s but got '%s'" %
160 (key, str(expected_dict[key]), got_dict[key]))
161 return False
162 else:
163 logging.info("The expected_dict is neither a str nor a dict.")
164 return False
165 return True
166
167
168 def _str_action(self, action):
169 """Convert the action function into a readable string.
170
171 The simple str() doesn't work on remote objects since we disable
172 allow_dotted_names flag when we launch the SimpleXMLRPCServer.
173 So this function handles the exception in this case.
174
175 Args:
176 action: A function.
177
178 Returns:
179 A readable string.
180 """
181 try:
182 return str(action)
183 except xmlrpclib.Fault:
184 return '<remote method>'
185
186
187 def _call_action(self, action_tuple):
188 """Call the action function with/without arguments.
189
190 Args:
191 action_tuple: A function, or a tuple which consisted of a function
192 and its arguments (if any).
193
194 Returns:
195 The result value of the action function.
196 """
197 if isinstance(action_tuple, tuple):
198 action = action_tuple[0]
199 args = action_tuple[1:]
200 if callable(action):
201 logging.info('calling %s with parameter %s' % (
202 self._str_action(action), str(action_tuple[1])))
203 return action(*args)
204 else:
205 logging.info('action is not callable!')
206 else:
207 action = action_tuple
208 if action is not None:
209 if callable(action):
210 logging.info('calling %s' % self._str_action(action))
211 return action()
212 else:
213 logging.info('action is not callable!')
214
215 return None
216
217
218 def register_faft_template(self, template):
219 """Register FAFT template, the default FAFT_STEP of each step.
220
221 Args:
222 template: A FAFT_STEP dict.
223 """
224 self._faft_template = template
225
226
227 def register_faft_sequence(self, sequence):
228 """Register FAFT sequence.
229
230 Args:
231 sequence: A FAFT_SEQUENCE array which consisted of FAFT_STEP dicts.
232 """
233 self._faft_sequence = sequence
234
235
236 def run_faft_sequence(self):
237 """Run FAFT sequence.
238
239 Raises:
240 error.TestFail: An error when the test failed.
241 """
242 default_test = self._faft_template
243 sequence = self._faft_sequence
244
245 for test in sequence:
246 cur_test = {}
247 cur_test.update(default_test)
248 cur_test.update(test)
249
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800250 if cur_test['state_checker']:
251 if not self._call_action(cur_test['state_checker']):
252 raise error.TestFail('State checker failed!')
253
254 self._call_action(cur_test['userspace_action'])
255
256 # Don't run reboot_action and firmware_action of the last step.
257 if test is not sequence[-1]:
258 self._call_action(cur_test['reboot_action'])
259 self.wait_for_client_offline()
260 self._call_action(cur_test['firmware_action'])
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800261
262 if 'install_deps_after_boot' in cur_test:
263 self.wait_for_client(
264 install_deps=cur_test['install_deps_after_boot'])
265 else:
266 self.wait_for_client()