Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(71)

Side by Side Diff: client/tests/kvm/kvm_config.py

Issue 6539001: Merge remote branch 'cros/upstream' into master. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/autotest.git@master
Patch Set: patch Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « client/tests/kvm/control.parallel ('k') | client/tests/kvm/kvm_preprocessing.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 """ 2 """
3 KVM configuration file utility functions. 3 KVM test configuration file parser
4 4
5 @copyright: Red Hat 2008-2010 5 @copyright: Red Hat 2008-2011
6 """ 6 """
7 7
8 import logging, re, os, sys, optparse, array, traceback, cPickle 8 import re, os, sys, optparse, collections, string
9 import common
10 import kvm_utils
11 from autotest_lib.client.common_lib import error
12 from autotest_lib.client.common_lib import logging_manager
13 9
14 10
15 class config: 11 # Filter syntax:
12 # , means OR
13 # .. means AND
14 # . means IMMEDIATELY-FOLLOWED-BY
15
16 # Example:
17 # qcow2..Fedora.14, RHEL.6..raw..boot, smp2..qcow2..migrate..ide
18 # means match all dicts whose names have:
19 # (qcow2 AND (Fedora IMMEDIATELY-FOLLOWED-BY 14)) OR
20 # ((RHEL IMMEDIATELY-FOLLOWED-BY 6) AND raw AND boot) OR
21 # (smp2 AND qcow2 AND migrate AND ide)
22
23 # Note:
24 # 'qcow2..Fedora.14' is equivalent to 'Fedora.14..qcow2'.
25 # 'qcow2..Fedora.14' is not equivalent to 'qcow2..14.Fedora'.
26 # 'ide, scsi' is equivalent to 'scsi, ide'.
27
28 # Filters can be used in 3 ways:
29 # only <filter>
30 # no <filter>
31 # <filter>:
32 # The last one starts a conditional block.
33
34
35 class ParserError:
36 def __init__(self, msg, line=None, filename=None, linenum=None):
37 self.msg = msg
38 self.line = line
39 self.filename = filename
40 self.linenum = linenum
41
42 def __str__(self):
43 if self.line:
44 return "%s: %r (%s:%s)" % (self.msg, self.line,
45 self.filename, self.linenum)
46 else:
47 return "%s (%s:%s)" % (self.msg, self.filename, self.linenum)
48
49
50 num_failed_cases = 5
51
52
53 class Node(object):
54 def __init__(self):
55 self.name = []
56 self.dep = []
57 self.content = []
58 self.children = []
59 self.labels = set()
60 self.append_to_shortname = False
61 self.failed_cases = collections.deque()
62
63
64 def _match_adjacent(block, ctx, ctx_set):
65 # TODO: explain what this function does
66 if block[0] not in ctx_set:
67 return 0
68 if len(block) == 1:
69 return 1
70 if block[1] not in ctx_set:
71 return int(ctx[-1] == block[0])
72 k = 0
73 i = ctx.index(block[0])
74 while i < len(ctx):
75 if k > 0 and ctx[i] != block[k]:
76 i -= k - 1
77 k = 0
78 if ctx[i] == block[k]:
79 k += 1
80 if k >= len(block):
81 break
82 if block[k] not in ctx_set:
83 break
84 i += 1
85 return k
86
87
88 def _might_match_adjacent(block, ctx, ctx_set, descendant_labels):
89 matched = _match_adjacent(block, ctx, ctx_set)
90 for elem in block[matched:]:
91 if elem not in descendant_labels:
92 return False
93 return True
94
95
96 # Filter must inherit from object (otherwise type() won't work)
97 class Filter(object):
98 def __init__(self, s):
99 self.filter = []
100 for char in s:
101 if not (char.isalnum() or char.isspace() or char in ".,_-"):
102 raise ParserError("Illegal characters in filter")
103 for word in s.replace(",", " ").split():
104 word = [block.split(".") for block in word.split("..")]
105 for block in word:
106 for elem in block:
107 if not elem:
108 raise ParserError("Syntax error")
109 self.filter += [word]
110
111
112 def match(self, ctx, ctx_set):
113 for word in self.filter:
114 for block in word:
115 if _match_adjacent(block, ctx, ctx_set) != len(block):
116 break
117 else:
118 return True
119 return False
120
121
122 def might_match(self, ctx, ctx_set, descendant_labels):
123 for word in self.filter:
124 for block in word:
125 if not _might_match_adjacent(block, ctx, ctx_set,
126 descendant_labels):
127 break
128 else:
129 return True
130 return False
131
132
133 class NoOnlyFilter(Filter):
134 def __init__(self, line):
135 Filter.__init__(self, line.split(None, 1)[1])
136 self.line = line
137
138
139 class OnlyFilter(NoOnlyFilter):
140 def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set,
141 descendant_labels):
142 for word in self.filter:
143 for block in word:
144 if (_match_adjacent(block, ctx, ctx_set) >
145 _match_adjacent(block, failed_ctx, failed_ctx_set)):
146 return self.might_match(ctx, ctx_set, descendant_labels)
147 return False
148
149
150 class NoFilter(NoOnlyFilter):
151 def might_pass(self, failed_ctx, failed_ctx_set, ctx, ctx_set,
152 descendant_labels):
153 for word in self.filter:
154 for block in word:
155 if (_match_adjacent(block, ctx, ctx_set) <
156 _match_adjacent(block, failed_ctx, failed_ctx_set)):
157 return not self.match(ctx, ctx_set)
158 return False
159
160
161 class Condition(NoFilter):
162 def __init__(self, line):
163 Filter.__init__(self, line.rstrip(":"))
164 self.line = line
165 self.content = []
166
167
168 class Parser(object):
16 """ 169 """
17 Parse an input file or string that follows the KVM Test Config File format 170 Parse an input file or string that follows the KVM Test Config File format
18 and generate a list of dicts that will be later used as configuration 171 and generate a list of dicts that will be later used as configuration
19 parameters by the KVM tests. 172 parameters by the KVM tests.
20 173
21 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File 174 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File
22 """ 175 """
23 176
24 def __init__(self, filename=None, debug=True): 177 def __init__(self, filename=None, debug=False):
25 """ 178 """
26 Initialize the list and optionally parse a file. 179 Initialize the parser and optionally parse a file.
27 180
28 @param filename: Path of the file that will be taken. 181 @param filename: Path of the file to parse.
29 @param debug: Whether to turn on debugging output. 182 @param debug: Whether to turn on debugging output.
30 """ 183 """
31 self.list = [array.array("H", [4, 4, 4, 4])] 184 self.node = Node()
32 self.object_cache = []
33 self.object_cache_indices = {}
34 self.regex_cache = {}
35 self.debug = debug 185 self.debug = debug
36 if filename: 186 if filename:
37 self.parse_file(filename) 187 self.parse_file(filename)
38 188
39 189
40 def parse_file(self, filename): 190 def parse_file(self, filename):
41 """ 191 """
42 Parse file. If it doesn't exist, raise an IOError. 192 Parse a file.
43 193
44 @param filename: Path of the configuration file. 194 @param filename: Path of the configuration file.
45 """ 195 """
46 if not os.path.exists(filename): 196 self.node = self._parse(FileReader(filename), self.node)
47 raise IOError("File %s not found" % filename) 197
48 str = open(filename).read() 198
49 self.list = self.parse(configreader(filename, str), self.list) 199 def parse_string(self, s):
50
51
52 def parse_string(self, str):
53 """ 200 """
54 Parse a string. 201 Parse a string.
55 202
56 @param str: String to parse. 203 @param s: String to parse.
57 """ 204 """
58 self.list = self.parse(configreader('<string>', str, real_file=False), s elf.list) 205 self.node = self._parse(StrReader(s), self.node)
59 206
60 207
61 def fork_and_parse(self, filename=None, str=None): 208 def get_dicts(self, node=None, ctx=[], content=[], shortname=[], dep=[]):
62 """
63 Parse a file and/or a string in a separate process to save memory.
64
65 Python likes to keep memory to itself even after the objects occupying
66 it have been destroyed. If during a call to parse_file() or
67 parse_string() a lot of memory is used, it can only be freed by
68 terminating the process. This function works around the problem by
69 doing the parsing in a forked process and then terminating it, freeing
70 any unneeded memory.
71
72 Note: if an exception is raised during parsing, its information will be
73 printed, and the resulting list will be empty. The exception will not
74 be raised in the process calling this function.
75
76 @param filename: Path of file to parse (optional).
77 @param str: String to parse (optional).
78 """
79 r, w = os.pipe()
80 r, w = os.fdopen(r, "r"), os.fdopen(w, "w")
81 pid = os.fork()
82 if not pid:
83 # Child process
84 r.close()
85 try:
86 if filename:
87 self.parse_file(filename)
88 if str:
89 self.parse_string(str)
90 except:
91 traceback.print_exc()
92 self.list = []
93 # Convert the arrays to strings before pickling because at least
94 # some Python versions can't pickle/unpickle arrays
95 l = [a.tostring() for a in self.list]
96 cPickle.dump((l, self.object_cache), w, -1)
97 w.close()
98 os._exit(0)
99 else:
100 # Parent process
101 w.close()
102 (l, self.object_cache) = cPickle.load(r)
103 r.close()
104 os.waitpid(pid, 0)
105 self.list = []
106 for s in l:
107 a = array.array("H")
108 a.fromstring(s)
109 self.list.append(a)
110
111
112 def get_generator(self):
113 """ 209 """
114 Generate dictionaries from the code parsed so far. This should 210 Generate dictionaries from the code parsed so far. This should
115 probably be called after parsing something. 211 be called after parsing something.
116 212
117 @return: A dict generator. 213 @return: A dict generator.
118 """ 214 """
119 for a in self.list: 215 def process_content(content, failed_filters):
120 name, shortname, depend, content = _array_get_all(a, 216 # 1. Check that the filters in content are OK with the current
121 self.object_cache) 217 # context (ctx).
122 dict = {"name": name, "shortname": shortname, "depend": depend} 218 # 2. Move the parts of content that are still relevant into
123 self._apply_content_to_dict(dict, content) 219 # new_content and unpack conditional blocks if appropriate.
124 yield dict 220 # For example, if an 'only' statement fully matches ctx, it
125 221 # becomes irrelevant and is not appended to new_content.
126 222 # If a conditional block fully matches, its contents are
127 def get_list(self): 223 # unpacked into new_content.
128 """ 224 # 3. Move failed filters into failed_filters, so that next time we
129 Generate a list of dictionaries from the code parsed so far. 225 # reach this node or one of its ancestors, we'll check those
130 This should probably be called after parsing something. 226 # filters first.
131 227 for t in content:
132 @return: A list of dicts. 228 filename, linenum, obj = t
133 """ 229 if type(obj) is str:
134 return list(self.get_generator()) 230 new_content.append(t)
135 231 continue
136 232 elif type(obj) is OnlyFilter:
137 def count(self, filter=".*"): 233 if not obj.might_match(ctx, ctx_set, labels):
138 """ 234 self._debug(" filter did not pass: %r (%s:%s)",
139 Return the number of dictionaries whose names match filter. 235 obj.line, filename, linenum)
140 236 failed_filters.append(t)
141 @param filter: A regular expression string. 237 return False
142 """ 238 elif obj.match(ctx, ctx_set):
143 exp = self._get_filter_regex(filter) 239 continue
240 elif type(obj) is NoFilter:
241 if obj.match(ctx, ctx_set):
242 self._debug(" filter did not pass: %r (%s:%s)",
243 obj.line, filename, linenum)
244 failed_filters.append(t)
245 return False
246 elif not obj.might_match(ctx, ctx_set, labels):
247 continue
248 elif type(obj) is Condition:
249 if obj.match(ctx, ctx_set):
250 self._debug(" conditional block matches: %r (%s:%s)",
251 obj.line, filename, linenum)
252 # Check and unpack the content inside this Condition
253 # object (note: the failed filters should go into
254 # new_internal_filters because we don't expect them to
255 # come from outside this node, even if the Condition
256 # itself was external)
257 if not process_content(obj.content,
258 new_internal_filters):
259 failed_filters.append(t)
260 return False
261 continue
262 elif not obj.might_match(ctx, ctx_set, labels):
263 continue
264 new_content.append(t)
265 return True
266
267 def might_pass(failed_ctx,
268 failed_ctx_set,
269 failed_external_filters,
270 failed_internal_filters):
271 for t in failed_external_filters:
272 if t not in content:
273 return True
274 filename, linenum, filter = t
275 if filter.might_pass(failed_ctx, failed_ctx_set, ctx, ctx_set,
276 labels):
277 return True
278 for t in failed_internal_filters:
279 filename, linenum, filter = t
280 if filter.might_pass(failed_ctx, failed_ctx_set, ctx, ctx_set,
281 labels):
282 return True
283 return False
284
285 def add_failed_case():
286 node.failed_cases.appendleft((ctx, ctx_set,
287 new_external_filters,
288 new_internal_filters))
289 if len(node.failed_cases) > num_failed_cases:
290 node.failed_cases.pop()
291
292 node = node or self.node
293 # Update dep
294 for d in node.dep:
295 dep = dep + [".".join(ctx + [d])]
296 # Update ctx
297 ctx = ctx + node.name
298 ctx_set = set(ctx)
299 labels = node.labels
300 # Get the current name
301 name = ".".join(ctx)
302 if node.name:
303 self._debug("checking out %r", name)
304 # Check previously failed filters
305 for i, failed_case in enumerate(node.failed_cases):
306 if not might_pass(*failed_case):
307 self._debug(" this subtree has failed before")
308 del node.failed_cases[i]
309 node.failed_cases.appendleft(failed_case)
310 return
311 # Check content and unpack it into new_content
312 new_content = []
313 new_external_filters = []
314 new_internal_filters = []
315 if (not process_content(node.content, new_internal_filters) or
316 not process_content(content, new_external_filters)):
317 add_failed_case()
318 return
319 # Update shortname
320 if node.append_to_shortname:
321 shortname = shortname + node.name
322 # Recurse into children
144 count = 0 323 count = 0
145 for a in self.list: 324 for n in node.children:
146 name = _array_get_name(a, self.object_cache) 325 for d in self.get_dicts(n, ctx, new_content, shortname, dep):
147 if exp.search(name):
148 count += 1 326 count += 1
149 return count 327 yield d
150 328 # Reached leaf?
151 329 if not node.children:
152 def parse_variants(self, cr, list, subvariants=False, prev_indent=-1): 330 self._debug(" reached leaf, returning it")
153 """ 331 d = {"name": name, "dep": dep, "shortname": ".".join(shortname)}
154 Read and parse lines from a configreader object until a line with an 332 for filename, linenum, op in new_content:
333 op.apply_to_dict(d, ctx, ctx_set)
334 yield d
335 # If this node did not produce any dicts, remember the failed filters
336 # of its descendants
337 elif not count:
338 new_external_filters = []
339 new_internal_filters = []
340 for n in node.children:
341 (failed_ctx,
342 failed_ctx_set,
343 failed_external_filters,
344 failed_internal_filters) = n.failed_cases[0]
345 for obj in failed_internal_filters:
346 if obj not in new_internal_filters:
347 new_internal_filters.append(obj)
348 for obj in failed_external_filters:
349 if obj in content:
350 if obj not in new_external_filters:
351 new_external_filters.append(obj)
352 else:
353 if obj not in new_internal_filters:
354 new_internal_filters.append(obj)
355 add_failed_case()
356
357
358 def _debug(self, s, *args):
359 if self.debug:
360 s = "DEBUG: %s" % s
361 print s % args
362
363
364 def _warn(self, s, *args):
365 s = "WARNING: %s" % s
366 print s % args
367
368
369 def _parse_variants(self, cr, node, prev_indent=-1):
370 """
371 Read and parse lines from a FileReader object until a line with an
155 indent level lower than or equal to prev_indent is encountered. 372 indent level lower than or equal to prev_indent is encountered.
156 373
157 @brief: Parse a 'variants' or 'subvariants' block from a configreader 374 @param cr: A FileReader/StrReader object.
158 object. 375 @param node: A node to operate on.
159 @param cr: configreader object to be parsed.
160 @param list: List of arrays to operate on.
161 @param subvariants: If True, parse in 'subvariants' mode;
162 otherwise parse in 'variants' mode.
163 @param prev_indent: The indent level of the "parent" block. 376 @param prev_indent: The indent level of the "parent" block.
164 @return: The resulting list of arrays. 377 @return: A node object.
165 """ 378 """
166 new_list = [] 379 node4 = Node()
167 380
168 while True: 381 while True:
169 pos = cr.tell() 382 line, indent, linenum = cr.get_next_line(prev_indent)
170 (indented_line, line, indent) = cr.get_next_line() 383 if not line:
171 if indent <= prev_indent:
172 cr.seek(pos)
173 break 384 break
174 385
175 # Get name and dependencies 386 name, dep = map(str.strip, line.lstrip("- ").split(":", 1))
176 (name, depend) = map(str.strip, line.lstrip("- ").split(":")) 387 for char in name:
177 388 if not (char.isalnum() or char in "@._-"):
178 # See if name should be added to the 'shortname' field 389 raise ParserError("Illegal characters in variant name",
179 add_to_shortname = not name.startswith("@") 390 line, cr.filename, linenum)
180 name = name.lstrip("@") 391 for char in dep:
181 392 if not (char.isalnum() or char.isspace() or char in ".,_-"):
182 # Store name and dependencies in cache and get their indices 393 raise ParserError("Illegal characters in dependencies",
183 n = self._store_str(name) 394 line, cr.filename, linenum)
184 d = self._store_str(depend) 395
185 396 node2 = Node()
186 # Make a copy of list 397 node2.children = [node]
187 temp_list = [a[:] for a in list] 398 node2.labels = node.labels
188 399
189 if subvariants: 400 node3 = self._parse(cr, node2, prev_indent=indent)
190 # If we're parsing 'subvariants', first modify the list 401 node3.name = name.lstrip("@").split(".")
191 if add_to_shortname: 402 node3.dep = dep.replace(",", " ").split()
192 for a in temp_list: 403 node3.append_to_shortname = not name.startswith("@")
193 _array_append_to_name_shortname_depend(a, n, d) 404
194 else: 405 node4.children += [node3]
195 for a in temp_list: 406 node4.labels.update(node3.labels)
196 _array_append_to_name_depend(a, n, d) 407 node4.labels.update(node3.name)
197 temp_list = self.parse(cr, temp_list, restricted=True, 408
198 prev_indent=indent) 409 return node4
199 else: 410
200 # If we're parsing 'variants', parse before modifying the list 411
201 if self.debug: 412 def _parse(self, cr, node, prev_indent=-1):
202 _debug_print(indented_line, 413 """
203 "Entering variant '%s' " 414 Read and parse lines from a StrReader object until a line with an
204 "(variant inherits %d dicts)" %
205 (name, len(list)))
206 temp_list = self.parse(cr, temp_list, restricted=False,
207 prev_indent=indent)
208 if add_to_shortname:
209 for a in temp_list:
210 _array_prepend_to_name_shortname_depend(a, n, d)
211 else:
212 for a in temp_list:
213 _array_prepend_to_name_depend(a, n, d)
214
215 new_list += temp_list
216
217 return new_list
218
219
220 def parse(self, cr, list, restricted=False, prev_indent=-1):
221 """
222 Read and parse lines from a configreader object until a line with an
223 indent level lower than or equal to prev_indent is encountered. 415 indent level lower than or equal to prev_indent is encountered.
224 416
225 @brief: Parse a configreader object. 417 @param cr: A FileReader/StrReader object.
226 @param cr: A configreader object. 418 @param node: A Node or a Condition object to operate on.
227 @param list: A list of arrays to operate on (list is modified in
228 place and should not be used after the call).
229 @param restricted: If True, operate in restricted mode
230 (prohibit 'variants').
231 @param prev_indent: The indent level of the "parent" block. 419 @param prev_indent: The indent level of the "parent" block.
232 @return: The resulting list of arrays. 420 @return: A node object.
233 @note: List is destroyed and should not be used after the call. 421 """
234 Only the returned list should be used.
235 """
236 current_block = ""
237
238 while True: 422 while True:
239 pos = cr.tell() 423 line, indent, linenum = cr.get_next_line(prev_indent)
240 (indented_line, line, indent) = cr.get_next_line() 424 if not line:
241 if indent <= prev_indent:
242 cr.seek(pos)
243 self._append_content_to_arrays(list, current_block)
244 break 425 break
245 426
246 len_list = len(list) 427 words = line.split(None, 1)
247
248 # Parse assignment operators (keep lines in temporary buffer)
249 if "=" in line:
250 if self.debug and not restricted:
251 _debug_print(indented_line,
252 "Parsing operator (%d dicts in current "
253 "context)" % len_list)
254 current_block += line + "\n"
255 continue
256
257 # Flush the temporary buffer
258 self._append_content_to_arrays(list, current_block)
259 current_block = ""
260
261 words = line.split()
262
263 # Parse 'no' and 'only' statements
264 if words[0] == "no" or words[0] == "only":
265 if len(words) <= 1:
266 continue
267 filters = map(self._get_filter_regex, words[1:])
268 filtered_list = []
269 if words[0] == "no":
270 for a in list:
271 name = _array_get_name(a, self.object_cache)
272 for filter in filters:
273 if filter.search(name):
274 break
275 else:
276 filtered_list.append(a)
277 if words[0] == "only":
278 for a in list:
279 name = _array_get_name(a, self.object_cache)
280 for filter in filters:
281 if filter.search(name):
282 filtered_list.append(a)
283 break
284 list = filtered_list
285 if self.debug and not restricted:
286 _debug_print(indented_line,
287 "Parsing no/only (%d dicts in current "
288 "context, %d remain)" %
289 (len_list, len(list)))
290 continue
291 428
292 # Parse 'variants' 429 # Parse 'variants'
293 if line == "variants:": 430 if line == "variants:":
294 # 'variants' not allowed in restricted mode 431 # 'variants' is not allowed inside a conditional block
295 # (inside an exception or inside subvariants) 432 if isinstance(node, Condition):
296 if restricted: 433 raise ParserError("'variants' is not allowed inside a "
297 e_msg = "Using variants in this context is not allowed" 434 "conditional block",
298 cr.raise_error(e_msg) 435 None, cr.filename, linenum)
299 if self.debug and not restricted: 436 node = self._parse_variants(cr, node, prev_indent=indent)
300 _debug_print(indented_line,
301 "Entering variants block (%d dicts in "
302 "current context)" % len_list)
303 list = self.parse_variants(cr, list, subvariants=False,
304 prev_indent=indent)
305 continue
306
307 # Parse 'subvariants' (the block is parsed for each dict
308 # separately)
309 if line == "subvariants:":
310 if self.debug and not restricted:
311 _debug_print(indented_line,
312 "Entering subvariants block (%d dicts in "
313 "current context)" % len_list)
314 new_list = []
315 # Remember current position
316 pos = cr.tell()
317 # Read the lines in any case
318 self.parse_variants(cr, [], subvariants=True,
319 prev_indent=indent)
320 # Iterate over the list...
321 for index in xrange(len(list)):
322 # Revert to initial position in this 'subvariants' block
323 cr.seek(pos)
324 # Everything inside 'subvariants' should be parsed in
325 # restricted mode
326 new_list += self.parse_variants(cr, list[index:index+1],
327 subvariants=True,
328 prev_indent=indent)
329 list = new_list
330 continue 437 continue
331 438
332 # Parse 'include' statements 439 # Parse 'include' statements
333 if words[0] == "include": 440 if words[0] == "include":
334 if len(words) <= 1: 441 if len(words) < 2:
442 raise ParserError("Syntax error: missing parameter",
443 line, cr.filename, linenum)
444 if not isinstance(cr, FileReader):
445 raise ParserError("Cannot include because no file is "
446 "currently open",
447 line, cr.filename, linenum)
448 filename = os.path.join(os.path.dirname(cr.filename), words[1])
449 if not os.path.isfile(filename):
450 self._warn("%r (%s:%s): file doesn't exist or is not a "
451 "regular file", line, cr.filename, linenum)
335 continue 452 continue
336 if self.debug and not restricted: 453 node = self._parse(FileReader(filename), node)
337 _debug_print(indented_line, "Entering file %s" % words[1])
338
339 cur_filename = cr.real_filename()
340 if cur_filename is None:
341 cr.raise_error("'include' is valid only when parsing a file" )
342
343 filename = os.path.join(os.path.dirname(cur_filename),
344 words[1])
345 if not os.path.exists(filename):
346 cr.raise_error("Cannot include %s -- file not found" % (file name))
347
348 str = open(filename).read()
349 list = self.parse(configreader(filename, str), list, restricted)
350 if self.debug and not restricted:
351 _debug_print("", "Leaving file %s" % words[1])
352
353 continue 454 continue
354 455
355 # Parse multi-line exceptions 456 # Parse 'only' and 'no' filters
356 # (the block is parsed for each dict separately) 457 if words[0] in ("only", "no"):
458 if len(words) < 2:
459 raise ParserError("Syntax error: missing parameter",
460 line, cr.filename, linenum)
461 try:
462 if words[0] == "only":
463 f = OnlyFilter(line)
464 elif words[0] == "no":
465 f = NoFilter(line)
466 except ParserError, e:
467 e.line = line
468 e.filename = cr.filename
469 e.linenum = linenum
470 raise
471 node.content += [(cr.filename, linenum, f)]
472 continue
473
474 # Parse conditional blocks
357 if line.endswith(":"): 475 if line.endswith(":"):
358 if self.debug and not restricted: 476 try:
359 _debug_print(indented_line, 477 cond = Condition(line)
360 "Entering multi-line exception block " 478 except ParserError, e:
361 "(%d dicts in current context outside " 479 e.line = line
362 "exception)" % len_list) 480 e.filename = cr.filename
363 line = line[:-1] 481 e.linenum = linenum
364 new_list = [] 482 raise
365 # Remember current position 483 self._parse(cr, cond, prev_indent=indent)
366 pos = cr.tell() 484 node.content += [(cr.filename, linenum, cond)]
367 # Read the lines in any case
368 self.parse(cr, [], restricted=True, prev_indent=indent)
369 # Iterate over the list...
370 exp = self._get_filter_regex(line)
371 for index in xrange(len(list)):
372 name = _array_get_name(list[index], self.object_cache)
373 if exp.search(name):
374 # Revert to initial position in this exception block
375 cr.seek(pos)
376 # Everything inside an exception should be parsed in
377 # restricted mode
378 new_list += self.parse(cr, list[index:index+1],
379 restricted=True,
380 prev_indent=indent)
381 else:
382 new_list.append(list[index])
383 list = new_list
384 continue 485 continue
385 486
386 return list 487 # Parse regular operators
387 488 try:
388 489 op = Op(line)
389 def _get_filter_regex(self, filter): 490 except ParserError, e:
390 """ 491 e.line = line
391 Return a regex object corresponding to a given filter string. 492 e.filename = cr.filename
392 493 e.linenum = linenum
393 All regular expressions given to the parser are passed through this 494 raise
394 function first. Its purpose is to make them more specific and better 495 node.content += [(cr.filename, linenum, op)]
395 suited to match dictionary names: it forces simple expressions to match 496
396 only between dots or at the beginning or end of a string. For example, 497 return node
397 the filter 'foo' will match 'foo.bar' but not 'foobar'.
398 """
399 try:
400 return self.regex_cache[filter]
401 except KeyError:
402 exp = re.compile(r"(\.|^)(%s)(\.|$)" % filter)
403 self.regex_cache[filter] = exp
404 return exp
405
406
407 def _store_str(self, str):
408 """
409 Store str in the internal object cache, if it isn't already there, and
410 return its identifying index.
411
412 @param str: String to store.
413 @return: The index of str in the object cache.
414 """
415 try:
416 return self.object_cache_indices[str]
417 except KeyError:
418 self.object_cache.append(str)
419 index = len(self.object_cache) - 1
420 self.object_cache_indices[str] = index
421 return index
422
423
424 def _append_content_to_arrays(self, list, content):
425 """
426 Append content (config code containing assignment operations) to a list
427 of arrays.
428
429 @param list: List of arrays to operate on.
430 @param content: String containing assignment operations.
431 """
432 if content:
433 str_index = self._store_str(content)
434 for a in list:
435 _array_append_to_content(a, str_index)
436
437
438 def _apply_content_to_dict(self, dict, content):
439 """
440 Apply the operations in content (config code containing assignment
441 operations) to a dict.
442
443 @param dict: Dictionary to operate on. Must have 'name' key.
444 @param content: String containing assignment operations.
445 """
446 for line in content.splitlines():
447 op_found = None
448 op_pos = len(line)
449 for op in ops:
450 pos = line.find(op)
451 if pos >= 0 and pos < op_pos:
452 op_found = op
453 op_pos = pos
454 if not op_found:
455 continue
456 (left, value) = map(str.strip, line.split(op_found, 1))
457 if value and ((value[0] == '"' and value[-1] == '"') or
458 (value[0] == "'" and value[-1] == "'")):
459 value = value[1:-1]
460 filters_and_key = map(str.strip, left.split(":"))
461 filters = filters_and_key[:-1]
462 key = filters_and_key[-1]
463 for filter in filters:
464 exp = self._get_filter_regex(filter)
465 if not exp.search(dict["name"]):
466 break
467 else:
468 ops[op_found](dict, key, value)
469 498
470 499
471 # Assignment operators 500 # Assignment operators
472 501
473 def _op_set(dict, key, value): 502 _reserved_keys = set(("name", "shortname", "dep"))
474 dict[key] = value 503
475 504
476 505 def _op_set(d, key, value):
477 def _op_append(dict, key, value): 506 if key not in _reserved_keys:
478 dict[key] = dict.get(key, "") + value 507 d[key] = value
479 508
480 509
481 def _op_prepend(dict, key, value): 510 def _op_append(d, key, value):
482 dict[key] = value + dict.get(key, "") 511 if key not in _reserved_keys:
483 512 d[key] = d.get(key, "") + value
484 513
485 def _op_regex_set(dict, exp, value): 514
486 exp = re.compile("^(%s)$" % exp) 515 def _op_prepend(d, key, value):
487 for key in dict: 516 if key not in _reserved_keys:
488 if exp.match(key): 517 d[key] = value + d.get(key, "")
489 dict[key] = value 518
490 519
491 520 def _op_regex_set(d, exp, value):
492 def _op_regex_append(dict, exp, value): 521 exp = re.compile("%s$" % exp)
493 exp = re.compile("^(%s)$" % exp) 522 for key in d:
494 for key in dict: 523 if key not in _reserved_keys and exp.match(key):
495 if exp.match(key): 524 d[key] = value
496 dict[key] += value 525
497 526
498 527 def _op_regex_append(d, exp, value):
499 def _op_regex_prepend(dict, exp, value): 528 exp = re.compile("%s$" % exp)
500 exp = re.compile("^(%s)$" % exp) 529 for key in d:
501 for key in dict: 530 if key not in _reserved_keys and exp.match(key):
502 if exp.match(key): 531 d[key] += value
503 dict[key] = value + dict[key] 532
504 533
505 534 def _op_regex_prepend(d, exp, value):
506 ops = { 535 exp = re.compile("%s$" % exp)
507 "=": _op_set, 536 for key in d:
508 "+=": _op_append, 537 if key not in _reserved_keys and exp.match(key):
509 "<=": _op_prepend, 538 d[key] = value + d[key]
510 "?=": _op_regex_set, 539
511 "?+=": _op_regex_append, 540
512 "?<=": _op_regex_prepend, 541 def _op_regex_del(d, empty, exp):
513 } 542 exp = re.compile("%s$" % exp)
514 543 for key in d.keys():
515 544 if key not in _reserved_keys and exp.match(key):
516 # Misc functions 545 del d[key]
517 546
518 def _debug_print(str1, str2=""): 547
548 _ops = {"=": (r"\=", _op_set),
549 "+=": (r"\+\=", _op_append),
550 "<=": (r"\<\=", _op_prepend),
551 "?=": (r"\?\=", _op_regex_set),
552 "?+=": (r"\?\+\=", _op_regex_append),
553 "?<=": (r"\?\<\=", _op_regex_prepend),
554 "del": (r"^del\b", _op_regex_del)}
555
556 _ops_exp = re.compile("|".join([op[0] for op in _ops.values()]))
557
558
559 class Op(object):
560 def __init__(self, line):
561 m = re.search(_ops_exp, line)
562 if not m:
563 raise ParserError("Syntax error: missing operator")
564 left = line[:m.start()].strip()
565 value = line[m.end():].strip()
566 if value and ((value[0] == '"' and value[-1] == '"') or
567 (value[0] == "'" and value[-1] == "'")):
568 value = value[1:-1]
569 filters_and_key = map(str.strip, left.split(":"))
570 self.filters = [Filter(f) for f in filters_and_key[:-1]]
571 self.key = filters_and_key[-1]
572 self.value = value
573 self.func = _ops[m.group()][1]
574
575
576 def apply_to_dict(self, d, ctx, ctx_set):
577 for f in self.filters:
578 if not f.match(ctx, ctx_set):
579 return
580 self.func(d, self.key, self.value)
581
582
583 # StrReader and FileReader
584
585 class StrReader(object):
519 """ 586 """
520 Nicely print two strings and an arrow. 587 Preprocess an input string for easy reading.
521
522 @param str1: First string.
523 @param str2: Second string.
524 """ 588 """
525 if str2: 589 def __init__(self, s):
526 str = "%-50s ---> %s" % (str1, str2)
527 else:
528 str = str1
529 logging.debug(str)
530
531
532 # configreader
533
534 class configreader:
535 """
536 Preprocess an input string and provide file-like services.
537 This is intended as a replacement for the file and StringIO classes,
538 whose readline() and/or seek() methods seem to be slow.
539 """
540
541 def __init__(self, filename, str, real_file=True):
542 """ 590 """
543 Initialize the reader. 591 Initialize the reader.
544 592
545 @param filename: the filename we're parsing 593 @param s: The string to parse.
546 @param str: The string to parse. 594 """
547 @param real_file: Indicates if filename represents a real file. Defaults to True. 595 self.filename = "<string>"
548 """ 596 self._lines = []
549 self.filename = filename 597 self._line_index = 0
550 self.is_real_file = real_file 598 for linenum, line in enumerate(s.splitlines()):
551 self.line_index = 0
552 self.lines = []
553 self.real_number = []
554 for num, line in enumerate(str.splitlines()):
555 line = line.rstrip().expandtabs() 599 line = line.rstrip().expandtabs()
556 stripped_line = line.strip() 600 stripped_line = line.lstrip()
557 indent = len(line) - len(stripped_line) 601 indent = len(line) - len(stripped_line)
558 if (not stripped_line 602 if (not stripped_line
559 or stripped_line.startswith("#") 603 or stripped_line.startswith("#")
560 or stripped_line.startswith("//")): 604 or stripped_line.startswith("//")):
561 continue 605 continue
562 self.lines.append((line, stripped_line, indent)) 606 self._lines.append((stripped_line, indent, linenum + 1))
563 self.real_number.append(num + 1) 607
564 608
565 609 def get_next_line(self, prev_indent):
566 def real_filename(self): 610 """
567 """Returns the filename we're reading, in case it is a real file 611 Get the next non-empty, non-comment line in the string, whose
568 612 indentation level is higher than prev_indent.
569 @returns the filename we are parsing, or None in case we're not parsing a real file 613
570 """ 614 @param prev_indent: The indentation level of the previous block.
571 if self.is_real_file: 615 @return: (line, indent, linenum), where indent is the line's
572 return self.filename 616 indentation level. If no line is available, (None, -1, -1) is
573 617 returned.
574 def get_next_line(self): 618 """
575 """ 619 if self._line_index >= len(self._lines):
576 Get the next non-empty, non-comment line in the string. 620 return None, -1, -1
577 621 line, indent, linenum = self._lines[self._line_index]
578 @param file: File like object. 622 if indent <= prev_indent:
579 @return: (line, stripped_line, indent), where indent is the line's 623 return None, -1, -1
580 indent level or -1 if no line is available. 624 self._line_index += 1
581 """ 625 return line, indent, linenum
582 try: 626
583 if self.line_index < len(self.lines): 627
584 return self.lines[self.line_index] 628 class FileReader(StrReader):
585 else: 629 """
586 return (None, None, -1) 630 Preprocess an input file for easy reading.
587 finally: 631 """
588 self.line_index += 1 632 def __init__(self, filename):
589 633 """
590 634 Initialize the reader.
591 def tell(self): 635
592 """ 636 @parse filename: The name of the input file.
593 Return the current line index. 637 """
594 """ 638 StrReader.__init__(self, open(filename).read())
595 return self.line_index 639 self.filename = filename
596 640
597 641
598 def seek(self, index): 642 if __name__ == "__main__":
599 """ 643 parser = optparse.OptionParser("usage: %prog [options] <filename>")
600 Set the current line index. 644 parser.add_option("-v", "--verbose", dest="debug", action="store_true",
601 """ 645 help="include debug messages in console output")
602 self.line_index = index 646 parser.add_option("-f", "--fullname", dest="fullname", action="store_true",
603 647 help="show full dict names instead of short names")
604 def raise_error(self, msg): 648 parser.add_option("-c", "--contents", dest="contents", action="store_true",
605 """Raise an error related to the last line returned by get_next_line() 649 help="show dict contents")
606 """ 650
607 if self.line_index == 0: # nothing was read. shouldn't happen, but... 651 options, args = parser.parse_args()
608 line_id = 'BEGIN' 652 if not args:
609 elif self.line_index >= len(self.lines): # past EOF 653 parser.error("filename required")
610 line_id = 'EOF' 654
655 c = Parser(args[0], debug=options.debug)
656 for i, d in enumerate(c.get_dicts()):
657 if options.fullname:
658 print "dict %4d: %s" % (i + 1, d["name"])
611 else: 659 else:
612 # line_index is the _next_ line. get the previous one 660 print "dict %4d: %s" % (i + 1, d["shortname"])
613 line_id = str(self.real_number[self.line_index-1]) 661 if options.contents:
614 raise error.AutotestError("%s:%s: %s" % (self.filename, line_id, msg)) 662 keys = d.keys()
615 663 keys.sort()
616 664 for key in keys:
617 # Array structure: 665 print " %s = %s" % (key, d[key])
618 # ----------------
619 # The first 4 elements contain the indices of the 4 segments.
620 # a[0] -- Index of beginning of 'name' segment (always 4).
621 # a[1] -- Index of beginning of 'shortname' segment.
622 # a[2] -- Index of beginning of 'depend' segment.
623 # a[3] -- Index of beginning of 'content' segment.
624 # The next elements in the array comprise the aforementioned segments:
625 # The 'name' segment begins with a[a[0]] and ends with a[a[1]-1].
626 # The 'shortname' segment begins with a[a[1]] and ends with a[a[2]-1].
627 # The 'depend' segment begins with a[a[2]] and ends with a[a[3]-1].
628 # The 'content' segment begins with a[a[3]] and ends at the end of the array.
629
630 # The following functions append/prepend to various segments of an array.
631
632 def _array_append_to_name_shortname_depend(a, name, depend):
633 a.insert(a[1], name)
634 a.insert(a[2] + 1, name)
635 a.insert(a[3] + 2, depend)
636 a[1] += 1
637 a[2] += 2
638 a[3] += 3
639
640
641 def _array_prepend_to_name_shortname_depend(a, name, depend):
642 a[1] += 1
643 a[2] += 2
644 a[3] += 3
645 a.insert(a[0], name)
646 a.insert(a[1], name)
647 a.insert(a[2], depend)
648
649
650 def _array_append_to_name_depend(a, name, depend):
651 a.insert(a[1], name)
652 a.insert(a[3] + 1, depend)
653 a[1] += 1
654 a[2] += 1
655 a[3] += 2
656
657
658 def _array_prepend_to_name_depend(a, name, depend):
659 a[1] += 1
660 a[2] += 1
661 a[3] += 2
662 a.insert(a[0], name)
663 a.insert(a[2], depend)
664
665
666 def _array_append_to_content(a, content):
667 a.append(content)
668
669
670 def _array_get_name(a, object_cache):
671 """
672 Return the name of a dictionary represented by a given array.
673
674 @param a: Array representing a dictionary.
675 @param object_cache: A list of strings referenced by elements in the array.
676 """
677 return ".".join([object_cache[i] for i in a[a[0]:a[1]]])
678
679
680 def _array_get_all(a, object_cache):
681 """
682 Return a 4-tuple containing all the data stored in a given array, in a
683 format that is easy to turn into an actual dictionary.
684
685 @param a: Array representing a dictionary.
686 @param object_cache: A list of strings referenced by elements in the array.
687 @return: A 4-tuple: (name, shortname, depend, content), in which all
688 members are strings except depend which is a list of strings.
689 """
690 name = ".".join([object_cache[i] for i in a[a[0]:a[1]]])
691 shortname = ".".join([object_cache[i] for i in a[a[1]:a[2]]])
692 content = "".join([object_cache[i] for i in a[a[3]:]])
693 depend = []
694 prefix = ""
695 for n, d in zip(a[a[0]:a[1]], a[a[2]:a[3]]):
696 for dep in object_cache[d].split():
697 depend.append(prefix + dep)
698 prefix += object_cache[n] + "."
699 return name, shortname, depend, content
700
701
702 if __name__ == "__main__":
703 parser = optparse.OptionParser("usage: %prog [options] [filename]")
704 parser.add_option('--verbose', dest="debug", action='store_true',
705 help='include debug messages in console output')
706
707 options, args = parser.parse_args()
708 debug = options.debug
709 if args:
710 filenames = args
711 else:
712 filenames = [os.path.join(os.path.dirname(sys.argv[0]), "tests.cfg")]
713
714 # Here we configure the stand alone program to use the autotest
715 # logging system.
716 logging_manager.configure_logging(kvm_utils.KvmLoggingConfig(),
717 verbose=debug)
718 cfg = config(debug=debug)
719 for fn in filenames:
720 cfg.parse_file(fn)
721 dicts = cfg.get_generator()
722 for i, dict in enumerate(dicts):
723 print "Dictionary #%d:" % (i)
724 keys = dict.keys()
725 keys.sort()
726 for key in keys:
727 print " %s = %s" % (key, dict[key])
OLDNEW
« no previous file with comments | « client/tests/kvm/control.parallel ('k') | client/tests/kvm/kvm_preprocessing.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698