blob: 99ccb2a28ee237fe7318dc10c2c1ed5d6808d7ef [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 """
86 filter = re.compile("(\\.|^)" + filter + "(\\.|$)")
87 return filter.search(dict["name"]) != None
88
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 """
97 if list == None:
98 list = self.list
99 filtered_list = []
100 for dict in list:
101 if self.match(filter, dict):
102 filtered_list.append(dict)
103 return filtered_list
104
105
106 # Currently unused, will be removed if it remains unused
107 def get_match_block_indices(self, filter, list=None):
108 """
109 Get the indexes of a list that match a given filter.
110
lmrfc0e1412009-06-10 20:03:26 +0000111 @param filter: A regular expression that will filter the list.
112 @param list: List which we want to know the indexes that match a filter.
lmr6f669ce2009-05-31 19:02:42 +0000113 """
114 if list == None:
115 list = self.list
116 block_list = []
117 prev_match = False
118 for index in range(len(list)):
119 dict = list[index]
120 if self.match(filter, dict):
121 if not prev_match:
122 block_list.append([index])
123 prev_match = True
124 else:
125 if prev_match:
126 block_list[-1].append(index)
127 prev_match = False
128 if prev_match:
129 block_list[-1].append(len(list))
130 return block_list
131
132
133 def split_and_strip(self, str, sep="="):
134 """
135 Split str and strip quotes from the resulting parts.
136
lmrfc0e1412009-06-10 20:03:26 +0000137 @param str: String that will be processed
138 @param sep: Separator that will be used to split the string
lmr6f669ce2009-05-31 19:02:42 +0000139 """
lmr0015c712009-06-08 13:51:14 +0000140 temp = str.split(sep, 1)
lmr6f669ce2009-05-31 19:02:42 +0000141 for i in range(len(temp)):
142 temp[i] = temp[i].strip()
lmr434252b2009-06-10 19:27:14 +0000143 if re.findall("^\".*\"$", temp[i]):
144 temp[i] = temp[i].strip("\"")
145 elif re.findall("^\'.*\'$", temp[i]):
146 temp[i] = temp[i].strip("\'")
lmr6f669ce2009-05-31 19:02:42 +0000147 return temp
148
149
150 def get_next_line(self, file):
151 """
152 Get the next non-empty, non-comment line in a file like object.
153
lmrfc0e1412009-06-10 20:03:26 +0000154 @param file: File like object
155 @return: If no line is available, return None.
lmr6f669ce2009-05-31 19:02:42 +0000156 """
157 while True:
158 line = file.readline()
159 if line == "": return None
160 stripped_line = line.strip()
161 if len(stripped_line) > 0 \
162 and not stripped_line.startswith('#') \
163 and not stripped_line.startswith('//'):
164 return line
165
166
167 def get_next_line_indent(self, file):
168 """
169 Return the indent level of the next non-empty, non-comment line in file.
170
171 @param file: File like object.
172 @return: If no line is available, return -1.
173 """
174 pos = file.tell()
175 line = self.get_next_line(file)
176 if not line:
177 file.seek(pos)
178 return -1
179 line = line.expandtabs()
180 indent = 0
181 while line[indent] == ' ':
182 indent += 1
183 file.seek(pos)
184 return indent
185
186
187 def add_name(self, str, name, append=False):
188 """
189 Add name to str with a separator dot and return the result.
190
191 @param str: String that will be processed
192 @param name: name that will be appended to the string.
193 @return: If append is True, append name to str.
194 Otherwise, pre-pend name to str.
195 """
196 if str == "":
197 return name
198 # Append?
199 elif append:
200 return str + "." + name
201 # Prepend?
202 else:
203 return name + "." + str
204
205
206 def parse_variants(self, file, list, subvariants=False, prev_indent=-1):
207 """
208 Read and parse lines from file like object until a line with an indent
209 level lower than or equal to prev_indent is encountered.
210
211 @brief: Parse a 'variants' or 'subvariants' block from a file-like
212 object.
213 @param file: File-like object that will be parsed
214 @param list: List of dicts to operate on
215 @param subvariants: If True, parse in 'subvariants' mode;
216 otherwise parse in 'variants' mode
217 @param prev_indent: The indent level of the "parent" block
218 @return: The resulting list of dicts.
219 """
220 new_list = []
221
222 while True:
223 indent = self.get_next_line_indent(file)
224 if indent <= prev_indent:
225 break
226 indented_line = self.get_next_line(file).rstrip()
227 line = indented_line.strip()
228
229 # Get name and dependencies
230 temp = line.strip("- ").split(":")
231 name = temp[0]
232 if len(temp) == 1:
233 dep_list = []
234 else:
235 dep_list = temp[1].split()
236
237 # See if name should be added to the 'shortname' field
238 add_to_shortname = True
239 if name.startswith("@"):
240 name = name.strip("@")
241 add_to_shortname = False
242
243 # Make a deep copy of list
244 temp_list = []
245 for dict in list:
246 new_dict = dict.copy()
247 new_dict["depend"] = dict["depend"][:]
248 temp_list.append(new_dict)
249
250 if subvariants:
251 # If we're parsing 'subvariants', we need to modify the list first
252 self.__modify_list_subvariants(temp_list, name, dep_list, add_to_shortname)
253 temp_list = self.parse(file, temp_list,
254 restricted=True, prev_indent=indent)
255 else:
256 # If we're parsing 'variants', we need to parse first and then modify the list
257 if self.debug:
258 self.__debug_print(indented_line, "Entering variant '%s' (variant inherits %d dicts)" % (name, len(list)))
259 temp_list = self.parse(file, temp_list,
260 restricted=False, prev_indent=indent)
261 self.__modify_list_variants(temp_list, name, dep_list, add_to_shortname)
262
263 new_list += temp_list
264
265 return new_list
266
267
268 def parse(self, file, list, restricted=False, prev_indent=-1):
269 """
270 Read and parse lines from file until a line with an indent level lower
271 than or equal to prev_indent is encountered.
272
273 @brief: Parse a file-like object.
274 @param file: A file-like object
275 @param list: A list of dicts to operate on (list is modified in
276 place and should not be used after the call)
277 @param restricted: if True, operate in restricted mode
278 (prohibit 'variants')
279 @param prev_indent: the indent level of the "parent" block
280 @return: Return the resulting list of dicts.
281 @note: List is destroyed and should not be used after the call.
282 Only the returned list should be used.
283 """
284 while True:
285 indent = self.get_next_line_indent(file)
286 if indent <= prev_indent:
287 break
288 indented_line = self.get_next_line(file).rstrip()
289 line = indented_line.strip()
290 words = line.split()
291
292 len_list = len(list)
293
294 # Look for a known operator in the line
295 operators = ["?+=", "?<=", "?=", "+=", "<=", "="]
296 op_found = None
lmr4f397fd2009-07-27 13:14:55 +0000297 op_pos = len(line)
lmr6f669ce2009-05-31 19:02:42 +0000298 for op in operators:
lmr4f397fd2009-07-27 13:14:55 +0000299 pos = line.find(op)
300 if pos >= 0 and pos < op_pos:
lmr6f669ce2009-05-31 19:02:42 +0000301 op_found = op
lmr4f397fd2009-07-27 13:14:55 +0000302 op_pos = pos
lmr6f669ce2009-05-31 19:02:42 +0000303
304 # Found an operator?
305 if op_found:
306 if self.debug and not restricted:
307 self.__debug_print(indented_line,
lmr4ae892a2009-06-10 20:01:49 +0000308 "Parsing operator (%d dicts in current "
lmr6f669ce2009-05-31 19:02:42 +0000309 "context)" % len_list)
310 (left, value) = self.split_and_strip(line, op_found)
311 filters_and_key = self.split_and_strip(left, ":")
312 filters = filters_and_key[:-1]
313 key = filters_and_key[-1]
314 filtered_list = list
315 for filter in filters:
316 filtered_list = self.filter(filter, filtered_list)
317 # Apply the operation to the filtered list
318 for dict in filtered_list:
319 if op_found == "=":
320 dict[key] = value
321 elif op_found == "+=":
322 dict[key] = dict.get(key, "") + value
323 elif op_found == "<=":
324 dict[key] = value + dict.get(key, "")
325 elif op_found.startswith("?") and dict.has_key(key):
326 if op_found == "?=":
327 dict[key] = value
328 elif op_found == "?+=":
329 dict[key] = dict.get(key, "") + value
330 elif op_found == "?<=":
331 dict[key] = value + dict.get(key, "")
332
333 # Parse 'no' and 'only' statements
334 elif words[0] == "no" or words[0] == "only":
335 if len(words) <= 1:
336 continue
337 filters = words[1:]
338 filtered_list = []
339 if words[0] == "no":
340 for dict in list:
341 for filter in filters:
342 if self.match(filter, dict):
343 break
344 else:
345 filtered_list.append(dict)
346 if words[0] == "only":
347 for dict in list:
348 for filter in filters:
349 if self.match(filter, dict):
350 filtered_list.append(dict)
351 break
352 list = filtered_list
353 if self.debug and not restricted:
354 self.__debug_print(indented_line,
lmr4ae892a2009-06-10 20:01:49 +0000355 "Parsing no/only (%d dicts in current "
lmr6f669ce2009-05-31 19:02:42 +0000356 "context, %d remain)" %
357 (len_list, len(list)))
358
359 # Parse 'variants'
360 elif line == "variants:":
361 # 'variants' not allowed in restricted mode
362 # (inside an exception or inside subvariants)
363 if restricted:
364 e_msg = "Using variants in this context is not allowed"
lmrc6bd3a62009-06-10 19:38:06 +0000365 raise error.AutotestError(e_msg)
lmr6f669ce2009-05-31 19:02:42 +0000366 if self.debug and not restricted:
367 self.__debug_print(indented_line,
368 "Entering variants block (%d dicts in"
369 "current context)" % len_list)
370 list = self.parse_variants(file, list, subvariants=False,
371 prev_indent=indent)
372
373 # Parse 'subvariants' (the block is parsed for each dict
374 # separately)
375 elif line == "subvariants:":
376 if self.debug and not restricted:
377 self.__debug_print(indented_line,
lmr4ae892a2009-06-10 20:01:49 +0000378 "Entering subvariants block (%d dicts in "
lmr6f669ce2009-05-31 19:02:42 +0000379 "current context)" % len_list)
380 new_list = []
381 # Remember current file position
382 pos = file.tell()
383 # Read the lines in any case
384 self.parse_variants(file, [], subvariants=True,
385 prev_indent=indent)
386 # Iterate over the list...
387 for index in range(len(list)):
388 # Revert to initial file position in this 'subvariants'
389 # block
390 file.seek(pos)
391 # Everything inside 'subvariants' should be parsed in
392 # restricted mode
393 new_list += self.parse_variants(file, list[index:index+1],
394 subvariants=True,
395 prev_indent=indent)
396 list = new_list
397
398 # Parse 'include' statements
399 elif words[0] == "include":
400 if len(words) <= 1:
401 continue
402 if self.debug and not restricted:
403 self.__debug_print(indented_line,
404 "Entering file %s" % words[1])
405 if self.filename:
406 filename = os.path.join(os.path.dirname(self.filename),
407 words[1])
408 if not os.path.exists(filename):
409 e_msg = "Cannot include %s -- file not found" % filename
410 raise error.AutotestError(e_msg)
411 new_file = open(filename, "r")
412 list = self.parse(new_file, list, restricted)
413 new_file.close()
414 if self.debug and not restricted:
415 self.__debug_print("", "Leaving file %s" % words[1])
416 else:
417 e_msg = "Cannot include anything because no file is open"
418 raise error.AutotestError(e_msg)
419
420 # Parse multi-line exceptions
421 # (the block is parsed for each dict separately)
422 elif line.endswith(":"):
423 if self.debug and not restricted:
424 self.__debug_print(indented_line,
425 "Entering multi-line exception block"
lmr4ae892a2009-06-10 20:01:49 +0000426 "(%d dicts in current context outside "
lmr6f669ce2009-05-31 19:02:42 +0000427 "exception)" % len_list)
428 line = line.strip(":")
429 new_list = []
430 # Remember current file position
431 pos = file.tell()
432 # Read the lines in any case
433 self.parse(file, [], restricted=True, prev_indent=indent)
434 # Iterate over the list...
435 for index in range(len(list)):
436 if self.match(line, list[index]):
437 # Revert to initial file position in this
438 # exception block
439 file.seek(pos)
440 # Everything inside an exception should be parsed in
441 # restricted mode
442 new_list += self.parse(file, list[index:index+1],
443 restricted=True, prev_indent=indent)
444 else:
445 new_list += list[index:index+1]
446 list = new_list
447
448 return list
449
450
451 def __debug_print(self, str1, str2=""):
452 """
453 Nicely print two strings and an arrow.
454
455 @param str1: First string
456 @param str2: Second string
457 """
458 if str2:
459 str = "%-50s ---> %s" % (str1, str2)
460 else:
461 str = str1
462 print str
463
464
465 def __modify_list_variants(self, list, name, dep_list, add_to_shortname):
466 """
467 Make some modifications to list, as part of parsing a 'variants' block.
468
469 @param list
470 """
471 for dict in list:
472 # Prepend name to the dict's 'name' field
473 dict["name"] = self.add_name(dict["name"], name)
474 # Prepend name to the dict's 'shortname' field
475 if add_to_shortname:
476 dict["shortname"] = self.add_name(dict["shortname"], name)
477 # Prepend name to each of the dict's dependencies
478 for i in range(len(dict["depend"])):
479 dict["depend"][i] = self.add_name(dict["depend"][i], name)
480 # Add new dependencies
481 dict["depend"] += dep_list
482
483
484 def __modify_list_subvariants(self, list, name, dep_list, add_to_shortname):
485 """
486 Make some modifications to list, as part of parsing a
487 'subvariants' block.
488
489 @param list: List that will be processed
490 @param name: Name that will be prepended to the dictionary name
491 @param dep_list: List of dependencies to be added to the list
492 dictionaries
493 @param add_to_shortname: Whether we'll add a shortname parameter to
494 the dictionaries.
495 """
496 for dict in list:
497 # Add new dependencies
498 for dep in dep_list:
499 dep_name = self.add_name(dict["name"], dep, append=True)
500 dict["depend"].append(dep_name)
501 # Append name to the dict's 'name' field
502 dict["name"] = self.add_name(dict["name"], name, append=True)
503 # Append name to the dict's 'shortname' field
504 if add_to_shortname:
505 dict["shortname"] = self.add_name(dict["shortname"], name,
506 append=True)
507
508
509if __name__ == "__main__":
510 if len(sys.argv) >= 2:
511 filename = sys.argv[1]
512 else:
513 filename = os.path.join(os.path.dirname(sys.argv[0]), "kvm_tests.cfg")
514 list = config(filename, debug=True).get_list()
515 i = 0
516 for dict in list:
517 print "Dictionary #%d:" % i
518 keys = dict.keys()
519 keys.sort()
520 for key in keys:
521 print " %s = %s" % (key, dict[key])
522 i += 1