blob: e478a55165e353e89e32e878e593592fd5b42c38 [file] [log] [blame]
lmrc6bd3a62009-06-10 19:38:06 +00001#!/usr/bin/python
lmr6f669ce2009-05-31 19:02:42 +00002import re, os, sys, StringIO
lmrc6bd3a62009-06-10 19:38:06 +00003import common
lmr6f669ce2009-05-31 19:02:42 +00004from autotest_lib.client.common_lib import error
5
6"""
7KVM configuration file utility functions.
8
9@copyright: Red Hat 2008-2009
10"""
11
12class config:
13 """
14 Parse an input file or string that follows the KVM Test Config File format
15 and generate a list of dicts that will be later used as configuration
16 parameters by the the KVM tests.
17
18 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File
19 """
20
21 def __init__(self, filename=None, debug=False):
22 """
23 Initialize the list and optionally parse filename.
24
lmrfc0e1412009-06-10 20:03:26 +000025 @param filename: Path of the file that will be taken
lmr6f669ce2009-05-31 19:02:42 +000026 """
27 self.list = [{"name": "", "shortname": "", "depend": []}]
28 self.debug = debug
29 self.filename = filename
30 if filename:
31 self.parse_file(filename)
32
33
34 def set_debug(self, debug=False):
35 """
36 Enable or disable debugging output.
37
lmrfc0e1412009-06-10 20:03:26 +000038 @param debug: Whether debug is enabled (True) or disabled (False).
lmr6f669ce2009-05-31 19:02:42 +000039 """
40 self.debug = debug
41
42
43 def parse_file(self, filename):
44 """
45 Parse filename, return the resulting list and store it in .list. If
46 filename does not exist, raise an exception.
47
lmrfc0e1412009-06-10 20:03:26 +000048 @param filename: Path of the configuration file.
lmr6f669ce2009-05-31 19:02:42 +000049 """
50 if not os.path.exists(filename):
51 raise Exception, "File %s not found" % filename
52 self.filename = filename
53 file = open(filename, "r")
54 self.list = self.parse(file, self.list)
55 file.close()
56 return self.list
57
58
59 def parse_string(self, str):
60 """
61 Parse a string, return the resulting list and store it in .list.
62
lmrfc0e1412009-06-10 20:03:26 +000063 @param str: String that will be parsed.
lmr6f669ce2009-05-31 19:02:42 +000064 """
65 file = StringIO.StringIO(str)
66 self.list = self.parse(file, self.list)
67 file.close()
68 return self.list
69
70
71 def get_list(self):
72 """
73 Return the list of dictionaries. This should probably be called after
74 parsing something.
75 """
76 return self.list
77
78
79 def match(self, filter, dict):
80 """
81 Return True if dict matches filter.
82
lmrfc0e1412009-06-10 20:03:26 +000083 @param filter: A regular expression that defines the filter.
84 @param dict: Dictionary that will be inspected.
lmr6f669ce2009-05-31 19:02:42 +000085 """
lmr5e419062009-08-07 21:04:35 +000086 filter = re.compile(r"(\.|^)" + filter + r"(\.|$)")
87 return bool(filter.search(dict["name"]))
lmr6f669ce2009-05-31 19:02:42 +000088
89
90 def filter(self, filter, list=None):
91 """
92 Filter a list of dicts.
93
lmrfc0e1412009-06-10 20:03:26 +000094 @param filter: A regular expression that will be used as a filter.
95 @param list: A list of dictionaries that will be filtered.
lmr6f669ce2009-05-31 19:02:42 +000096 """
lmr5e419062009-08-07 21:04:35 +000097 if list is None:
lmr6f669ce2009-05-31 19:02:42 +000098 list = self.list
lmr5e419062009-08-07 21:04:35 +000099 return [dict for dict in list if self.match(filter, dict)]
lmr6f669ce2009-05-31 19:02:42 +0000100
101
102 # Currently unused, will be removed if it remains unused
103 def get_match_block_indices(self, filter, list=None):
104 """
105 Get the indexes of a list that match a given filter.
106
lmrfc0e1412009-06-10 20:03:26 +0000107 @param filter: A regular expression that will filter the list.
108 @param list: List which we want to know the indexes that match a filter.
lmr6f669ce2009-05-31 19:02:42 +0000109 """
lmr5e419062009-08-07 21:04:35 +0000110 if list is None:
lmr6f669ce2009-05-31 19:02:42 +0000111 list = self.list
112 block_list = []
113 prev_match = False
114 for index in range(len(list)):
115 dict = list[index]
116 if self.match(filter, dict):
117 if not prev_match:
118 block_list.append([index])
119 prev_match = True
120 else:
121 if prev_match:
122 block_list[-1].append(index)
123 prev_match = False
124 if prev_match:
125 block_list[-1].append(len(list))
126 return block_list
127
128
129 def split_and_strip(self, str, sep="="):
130 """
131 Split str and strip quotes from the resulting parts.
132
lmrfc0e1412009-06-10 20:03:26 +0000133 @param str: String that will be processed
134 @param sep: Separator that will be used to split the string
lmr6f669ce2009-05-31 19:02:42 +0000135 """
lmr0015c712009-06-08 13:51:14 +0000136 temp = str.split(sep, 1)
lmr6f669ce2009-05-31 19:02:42 +0000137 for i in range(len(temp)):
138 temp[i] = temp[i].strip()
lmr434252b2009-06-10 19:27:14 +0000139 if re.findall("^\".*\"$", temp[i]):
140 temp[i] = temp[i].strip("\"")
141 elif re.findall("^\'.*\'$", temp[i]):
142 temp[i] = temp[i].strip("\'")
lmr6f669ce2009-05-31 19:02:42 +0000143 return temp
144
145
146 def get_next_line(self, file):
147 """
148 Get the next non-empty, non-comment line in a file like object.
149
lmrfc0e1412009-06-10 20:03:26 +0000150 @param file: File like object
151 @return: If no line is available, return None.
lmr6f669ce2009-05-31 19:02:42 +0000152 """
153 while True:
154 line = file.readline()
155 if line == "": return None
156 stripped_line = line.strip()
157 if len(stripped_line) > 0 \
158 and not stripped_line.startswith('#') \
159 and not stripped_line.startswith('//'):
160 return line
161
162
163 def get_next_line_indent(self, file):
164 """
165 Return the indent level of the next non-empty, non-comment line in file.
166
lmrf402d232009-07-27 13:16:12 +0000167 @param file: File like object.
168 @return: If no line is available, return -1.
lmr6f669ce2009-05-31 19:02:42 +0000169 """
170 pos = file.tell()
171 line = self.get_next_line(file)
172 if not line:
173 file.seek(pos)
174 return -1
175 line = line.expandtabs()
176 indent = 0
177 while line[indent] == ' ':
178 indent += 1
179 file.seek(pos)
180 return indent
181
182
183 def add_name(self, str, name, append=False):
184 """
185 Add name to str with a separator dot and return the result.
186
lmrf402d232009-07-27 13:16:12 +0000187 @param str: String that will be processed
188 @param name: name that will be appended to the string.
189 @return: If append is True, append name to str.
190 Otherwise, pre-pend name to str.
lmr6f669ce2009-05-31 19:02:42 +0000191 """
192 if str == "":
193 return name
194 # Append?
195 elif append:
196 return str + "." + name
197 # Prepend?
198 else:
199 return name + "." + str
200
201
202 def parse_variants(self, file, list, subvariants=False, prev_indent=-1):
203 """
204 Read and parse lines from file like object until a line with an indent
205 level lower than or equal to prev_indent is encountered.
206
lmrf402d232009-07-27 13:16:12 +0000207 @brief: Parse a 'variants' or 'subvariants' block from a file-like
208 object.
209 @param file: File-like object that will be parsed
210 @param list: List of dicts to operate on
211 @param subvariants: If True, parse in 'subvariants' mode;
212 otherwise parse in 'variants' mode
213 @param prev_indent: The indent level of the "parent" block
214 @return: The resulting list of dicts.
lmr6f669ce2009-05-31 19:02:42 +0000215 """
216 new_list = []
217
218 while True:
219 indent = self.get_next_line_indent(file)
220 if indent <= prev_indent:
221 break
222 indented_line = self.get_next_line(file).rstrip()
223 line = indented_line.strip()
224
225 # Get name and dependencies
226 temp = line.strip("- ").split(":")
227 name = temp[0]
228 if len(temp) == 1:
229 dep_list = []
230 else:
231 dep_list = temp[1].split()
232
233 # See if name should be added to the 'shortname' field
234 add_to_shortname = True
235 if name.startswith("@"):
236 name = name.strip("@")
237 add_to_shortname = False
238
239 # Make a deep copy of list
240 temp_list = []
241 for dict in list:
242 new_dict = dict.copy()
243 new_dict["depend"] = dict["depend"][:]
244 temp_list.append(new_dict)
245
246 if subvariants:
lmr5e419062009-08-07 21:04:35 +0000247 # If we're parsing 'subvariants', first modify the list
248 self.__modify_list_subvariants(temp_list, name, dep_list,
249 add_to_shortname)
lmr6f669ce2009-05-31 19:02:42 +0000250 temp_list = self.parse(file, temp_list,
251 restricted=True, prev_indent=indent)
252 else:
lmr5e419062009-08-07 21:04:35 +0000253 # If we're parsing 'variants', parse before modifying the list
lmr6f669ce2009-05-31 19:02:42 +0000254 if self.debug:
lmr5e419062009-08-07 21:04:35 +0000255 self.__debug_print(indented_line,
256 "Entering variant '%s' "
257 "(variant inherits %d dicts)" %
258 (name, len(list)))
lmr6f669ce2009-05-31 19:02:42 +0000259 temp_list = self.parse(file, temp_list,
260 restricted=False, prev_indent=indent)
lmr5e419062009-08-07 21:04:35 +0000261 self.__modify_list_variants(temp_list, name, dep_list,
262 add_to_shortname)
lmr6f669ce2009-05-31 19:02:42 +0000263
264 new_list += temp_list
265
266 return new_list
267
268
269 def parse(self, file, list, restricted=False, prev_indent=-1):
270 """
271 Read and parse lines from file until a line with an indent level lower
272 than or equal to prev_indent is encountered.
273
lmrf402d232009-07-27 13:16:12 +0000274 @brief: Parse a file-like object.
275 @param file: A file-like object
276 @param list: A list of dicts to operate on (list is modified in
277 place and should not be used after the call)
278 @param restricted: if True, operate in restricted mode
279 (prohibit 'variants')
280 @param prev_indent: the indent level of the "parent" block
281 @return: Return the resulting list of dicts.
282 @note: List is destroyed and should not be used after the call.
283 Only the returned list should be used.
lmr6f669ce2009-05-31 19:02:42 +0000284 """
285 while True:
286 indent = self.get_next_line_indent(file)
287 if indent <= prev_indent:
288 break
289 indented_line = self.get_next_line(file).rstrip()
290 line = indented_line.strip()
291 words = line.split()
292
293 len_list = len(list)
294
295 # Look for a known operator in the line
296 operators = ["?+=", "?<=", "?=", "+=", "<=", "="]
297 op_found = None
lmr4f397fd2009-07-27 13:14:55 +0000298 op_pos = len(line)
lmr6f669ce2009-05-31 19:02:42 +0000299 for op in operators:
lmr4f397fd2009-07-27 13:14:55 +0000300 pos = line.find(op)
301 if pos >= 0 and pos < op_pos:
lmr6f669ce2009-05-31 19:02:42 +0000302 op_found = op
lmr4f397fd2009-07-27 13:14:55 +0000303 op_pos = pos
lmr6f669ce2009-05-31 19:02:42 +0000304
305 # Found an operator?
306 if op_found:
307 if self.debug and not restricted:
308 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000309 "Parsing operator (%d dicts in current "
310 "context)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000311 (left, value) = self.split_and_strip(line, op_found)
312 filters_and_key = self.split_and_strip(left, ":")
313 filters = filters_and_key[:-1]
314 key = filters_and_key[-1]
315 filtered_list = list
316 for filter in filters:
317 filtered_list = self.filter(filter, filtered_list)
318 # Apply the operation to the filtered list
319 for dict in filtered_list:
320 if op_found == "=":
321 dict[key] = value
322 elif op_found == "+=":
323 dict[key] = dict.get(key, "") + value
324 elif op_found == "<=":
325 dict[key] = value + dict.get(key, "")
326 elif op_found.startswith("?") and dict.has_key(key):
327 if op_found == "?=":
328 dict[key] = value
329 elif op_found == "?+=":
330 dict[key] = dict.get(key, "") + value
331 elif op_found == "?<=":
332 dict[key] = value + dict.get(key, "")
333
334 # Parse 'no' and 'only' statements
335 elif words[0] == "no" or words[0] == "only":
336 if len(words) <= 1:
337 continue
338 filters = words[1:]
339 filtered_list = []
340 if words[0] == "no":
341 for dict in list:
342 for filter in filters:
343 if self.match(filter, dict):
344 break
345 else:
346 filtered_list.append(dict)
347 if words[0] == "only":
348 for dict in list:
349 for filter in filters:
350 if self.match(filter, dict):
351 filtered_list.append(dict)
352 break
353 list = filtered_list
354 if self.debug and not restricted:
355 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000356 "Parsing no/only (%d dicts in current "
357 "context, %d remain)" %
358 (len_list, len(list)))
lmr6f669ce2009-05-31 19:02:42 +0000359
360 # Parse 'variants'
361 elif line == "variants:":
362 # 'variants' not allowed in restricted mode
363 # (inside an exception or inside subvariants)
364 if restricted:
365 e_msg = "Using variants in this context is not allowed"
lmrc6bd3a62009-06-10 19:38:06 +0000366 raise error.AutotestError(e_msg)
lmr6f669ce2009-05-31 19:02:42 +0000367 if self.debug and not restricted:
368 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000369 "Entering variants block (%d dicts in "
370 "current context)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000371 list = self.parse_variants(file, list, subvariants=False,
372 prev_indent=indent)
373
374 # Parse 'subvariants' (the block is parsed for each dict
375 # separately)
376 elif line == "subvariants:":
377 if self.debug and not restricted:
378 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000379 "Entering subvariants block (%d dicts in "
380 "current context)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000381 new_list = []
382 # Remember current file position
383 pos = file.tell()
384 # Read the lines in any case
385 self.parse_variants(file, [], subvariants=True,
386 prev_indent=indent)
387 # Iterate over the list...
388 for index in range(len(list)):
389 # Revert to initial file position in this 'subvariants'
390 # block
391 file.seek(pos)
392 # Everything inside 'subvariants' should be parsed in
393 # restricted mode
394 new_list += self.parse_variants(file, list[index:index+1],
395 subvariants=True,
396 prev_indent=indent)
397 list = new_list
398
399 # Parse 'include' statements
400 elif words[0] == "include":
401 if len(words) <= 1:
402 continue
403 if self.debug and not restricted:
404 self.__debug_print(indented_line,
lmr5e419062009-08-07 21:04:35 +0000405 "Entering file %s" % words[1])
lmr6f669ce2009-05-31 19:02:42 +0000406 if self.filename:
407 filename = os.path.join(os.path.dirname(self.filename),
408 words[1])
409 if not os.path.exists(filename):
410 e_msg = "Cannot include %s -- file not found" % filename
411 raise error.AutotestError(e_msg)
412 new_file = open(filename, "r")
413 list = self.parse(new_file, list, restricted)
414 new_file.close()
415 if self.debug and not restricted:
416 self.__debug_print("", "Leaving file %s" % words[1])
417 else:
418 e_msg = "Cannot include anything because no file is open"
419 raise error.AutotestError(e_msg)
420
421 # Parse multi-line exceptions
422 # (the block is parsed for each dict separately)
423 elif line.endswith(":"):
424 if self.debug and not restricted:
425 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000426 "Entering multi-line exception block "
427 "(%d dicts in current context outside "
428 "exception)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000429 line = line.strip(":")
430 new_list = []
431 # Remember current file position
432 pos = file.tell()
433 # Read the lines in any case
434 self.parse(file, [], restricted=True, prev_indent=indent)
435 # Iterate over the list...
436 for index in range(len(list)):
437 if self.match(line, list[index]):
438 # Revert to initial file position in this
439 # exception block
440 file.seek(pos)
441 # Everything inside an exception should be parsed in
442 # restricted mode
443 new_list += self.parse(file, list[index:index+1],
lmr5e419062009-08-07 21:04:35 +0000444 restricted=True,
445 prev_indent=indent)
lmr6f669ce2009-05-31 19:02:42 +0000446 else:
447 new_list += list[index:index+1]
448 list = new_list
449
450 return list
451
452
453 def __debug_print(self, str1, str2=""):
454 """
455 Nicely print two strings and an arrow.
456
lmrf402d232009-07-27 13:16:12 +0000457 @param str1: First string
458 @param str2: Second string
lmr6f669ce2009-05-31 19:02:42 +0000459 """
460 if str2:
461 str = "%-50s ---> %s" % (str1, str2)
462 else:
463 str = str1
464 print str
465
466
467 def __modify_list_variants(self, list, name, dep_list, add_to_shortname):
468 """
469 Make some modifications to list, as part of parsing a 'variants' block.
470
lmrf402d232009-07-27 13:16:12 +0000471 @param list: List to be processed
472 @param name: Name to be prepended to the dictionary's 'name' key
473 @param dep_list: List of dependencies to be added to the dictionary's
474 'depend' key
475 @param add_to_shortname: Boolean indicating whether name should be
476 prepended to the dictionary's 'shortname' key as well
lmr6f669ce2009-05-31 19:02:42 +0000477 """
478 for dict in list:
479 # Prepend name to the dict's 'name' field
480 dict["name"] = self.add_name(dict["name"], name)
481 # Prepend name to the dict's 'shortname' field
482 if add_to_shortname:
483 dict["shortname"] = self.add_name(dict["shortname"], name)
484 # Prepend name to each of the dict's dependencies
485 for i in range(len(dict["depend"])):
486 dict["depend"][i] = self.add_name(dict["depend"][i], name)
487 # Add new dependencies
488 dict["depend"] += dep_list
489
490
491 def __modify_list_subvariants(self, list, name, dep_list, add_to_shortname):
492 """
lmrf402d232009-07-27 13:16:12 +0000493 Make some modifications to list, as part of parsing a 'subvariants'
494 block.
lmr6f669ce2009-05-31 19:02:42 +0000495
lmrf402d232009-07-27 13:16:12 +0000496 @param list: List to be processed
497 @param name: Name to be appended to the dictionary's 'name' key
498 @param dep_list: List of dependencies to be added to the dictionary's
499 'depend' key
500 @param add_to_shortname: Boolean indicating whether name should be
501 appended to the dictionary's 'shortname' as well
lmr6f669ce2009-05-31 19:02:42 +0000502 """
503 for dict in list:
504 # Add new dependencies
505 for dep in dep_list:
506 dep_name = self.add_name(dict["name"], dep, append=True)
507 dict["depend"].append(dep_name)
508 # Append name to the dict's 'name' field
509 dict["name"] = self.add_name(dict["name"], name, append=True)
510 # Append name to the dict's 'shortname' field
511 if add_to_shortname:
512 dict["shortname"] = self.add_name(dict["shortname"], name,
513 append=True)
514
515
516if __name__ == "__main__":
517 if len(sys.argv) >= 2:
518 filename = sys.argv[1]
519 else:
520 filename = os.path.join(os.path.dirname(sys.argv[0]), "kvm_tests.cfg")
521 list = config(filename, debug=True).get_list()
522 i = 0
523 for dict in list:
524 print "Dictionary #%d:" % i
525 keys = dict.keys()
526 keys.sort()
527 for key in keys:
528 print " %s = %s" % (key, dict[key])
529 i += 1